fix(16): repair broken connections, auth credentials, and dead code across 4 workflows
Phase 16 plans 16-02 through 16-05 introduced three classes of defects:
1. Connection keys used node IDs instead of node names (33 broken links
across n8n-workflow.json, n8n-batch-ui.json, n8n-actions.json)
2. GraphQL HTTP nodes used $env.UNRAID_API_KEY manual headers instead of
Header Auth credential, causing CSRF/UNAUTHENTICATED errors (20 nodes)
3. Duplicate node name "Execute Batch Update" (serial vs parallel paths)
Also fixes Build Cancel Return Submenu using $input.item.json instead of
$('Prepare Cancel From Confirm').item.json after GraphQL query chain.
Removes 12 dead/orphan nodes (6 pre-migration dead code chains,
6 unused utility templates). Node count: 193 -> 181.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+52
-32
@@ -88,10 +88,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -100,7 +96,9 @@
|
||||
"jsonBody": "={\"query\": \"query { docker { containers { id names state image } } }\"}",
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-get-containers",
|
||||
"name": "Query All Containers",
|
||||
@@ -110,7 +108,13 @@
|
||||
600,
|
||||
400
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -223,10 +227,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -235,7 +235,9 @@
|
||||
"jsonBody": "={{ JSON.stringify({query: $json.query}) }}",
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-start-container",
|
||||
"name": "Start Container",
|
||||
@@ -245,7 +247,13 @@
|
||||
1160,
|
||||
200
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -257,10 +265,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -269,7 +273,9 @@
|
||||
"jsonBody": "={{ JSON.stringify({query: $json.query}) }}",
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-stop-container",
|
||||
"name": "Stop Container",
|
||||
@@ -279,7 +285,13 @@
|
||||
1160,
|
||||
300
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -291,10 +303,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -303,7 +311,9 @@
|
||||
"jsonBody": "={{ JSON.stringify({query: $json.query}) }}",
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-stop-for-restart",
|
||||
"name": "Stop For Restart",
|
||||
@@ -313,7 +323,13 @@
|
||||
1160,
|
||||
400
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -469,10 +485,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -481,7 +493,9 @@
|
||||
"jsonBody": "={{ JSON.stringify({query: $json.query}) }}",
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-start-after-stop",
|
||||
"name": "Start After Stop",
|
||||
@@ -491,7 +505,13 @@
|
||||
1480,
|
||||
400
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -530,7 +550,7 @@
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Get All Containers",
|
||||
"node": "Query All Containers",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -732,4 +752,4 @@
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
-154
@@ -220,7 +220,7 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}",
|
||||
@@ -230,10 +230,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -244,7 +240,8 @@
|
||||
"errorHandling": "continueRegularOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-fetch-containers-mode",
|
||||
"name": "Fetch Containers For Mode",
|
||||
@@ -253,7 +250,13 @@
|
||||
"position": [
|
||||
680,
|
||||
100
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -316,7 +319,7 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}",
|
||||
@@ -326,10 +329,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -340,7 +339,8 @@
|
||||
"errorHandling": "continueRegularOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-fetch-containers-toggle",
|
||||
"name": "Fetch Containers For Update",
|
||||
@@ -349,7 +349,13 @@
|
||||
"position": [
|
||||
1120,
|
||||
100
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -368,7 +374,7 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}",
|
||||
@@ -378,10 +384,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -392,7 +394,8 @@
|
||||
"errorHandling": "continueRegularOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-fetch-containers-exec",
|
||||
"name": "Fetch Containers For Exec",
|
||||
@@ -401,7 +404,13 @@
|
||||
"position": [
|
||||
680,
|
||||
400
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -433,7 +442,7 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}",
|
||||
@@ -443,10 +452,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -457,7 +462,8 @@
|
||||
"errorHandling": "continueRegularOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-fetch-containers-nav",
|
||||
"name": "Fetch Containers For Nav",
|
||||
@@ -466,7 +472,13 @@
|
||||
"position": [
|
||||
900,
|
||||
300
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -498,7 +510,7 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}",
|
||||
@@ -508,10 +520,6 @@
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -522,7 +530,8 @@
|
||||
"errorHandling": "continueRegularOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-fetch-containers-clear",
|
||||
"name": "Fetch Containers For Clear",
|
||||
@@ -531,7 +540,13 @@
|
||||
"position": [
|
||||
900,
|
||||
500
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -687,7 +702,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Batch Keyboard",
|
||||
"node": "Normalize GraphQL Response (Mode)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -720,7 +735,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Rebuild Keyboard After Toggle",
|
||||
"node": "Normalize GraphQL Response (Toggle)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -731,7 +746,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Handle Exec",
|
||||
"node": "Normalize GraphQL Response (Exec)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -753,7 +768,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Rebuild Keyboard For Nav",
|
||||
"node": "Normalize GraphQL Response (Nav)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -775,25 +790,14 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Rebuild Keyboard After Clear",
|
||||
"node": "Normalize GraphQL Response (Clear)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-fetch-containers-mode": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize GraphQL Response (Mode)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-normalizer-mode": {
|
||||
"Normalize GraphQL Response (Mode)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
@@ -804,18 +808,7 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-fetch-containers-toggle": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize GraphQL Response (Toggle)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-normalizer-toggle": {
|
||||
"Normalize GraphQL Response (Toggle)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
@@ -826,18 +819,7 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-fetch-containers-exec": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize GraphQL Response (Exec)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-normalizer-exec": {
|
||||
"Normalize GraphQL Response (Exec)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
@@ -848,18 +830,7 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-fetch-containers-nav": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize GraphQL Response (Nav)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-normalizer-nav": {
|
||||
"Normalize GraphQL Response (Nav)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
@@ -870,18 +841,7 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-fetch-containers-clear": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalize GraphQL Response (Clear)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-normalizer-clear": {
|
||||
"Normalize GraphQL Response (Clear)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
@@ -891,63 +851,10 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"switch-route-batch-action": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Containers For Mode",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Fetch Containers For Exec",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"if-needs-keyboard-update": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Containers For Update",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-handle-nav": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Containers For Nav",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-handle-clear": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Containers For Clear",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-22
@@ -77,17 +77,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -96,7 +92,8 @@
|
||||
"jsonBody": "={\"query\": \"query { docker { containers { id names state image imageId } } }\"}",
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-query-containers",
|
||||
"name": "Query All Containers",
|
||||
@@ -106,7 +103,13 @@
|
||||
560,
|
||||
400
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -152,17 +155,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -171,7 +170,8 @@
|
||||
"jsonBody": "={{ {\"query\": \"query { docker { containers(filter: { id: \\\"\" + $json.containerId + \"\\\" }) { id names state image imageId } } }\"} }}",
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-query-single",
|
||||
"name": "Query Single Container",
|
||||
@@ -181,7 +181,13 @@
|
||||
560,
|
||||
300
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -226,17 +232,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -245,7 +247,8 @@
|
||||
"jsonBody": "={{ {\"query\": $json.query} }}",
|
||||
"options": {
|
||||
"timeout": 60000
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-update-container",
|
||||
"name": "Update Container",
|
||||
@@ -255,7 +258,13 @@
|
||||
1320,
|
||||
300
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -1091,4 +1100,4 @@
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+115
-366
@@ -429,46 +429,6 @@
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build the curl command for the action\nconst data = $input.item.json;\nconst containerId = data.matches[0].Id;\nconst action = data.action;\nconst containerName = data.matches[0].Name;\nconst chatId = data.chatId;\n\n// stop and restart use ?t=10 for graceful timeout\nconst timeout = (action === 'stop' || action === 'restart') ? '?t=10' : '';\n\n// Build curl command that returns HTTP status code\nconst cmd = `curl -s -o /dev/null -w \"%{http_code}\" --max-time 5 -X POST 'http://docker-socket-proxy:2375/v1.47/containers/${containerId}/${action}${timeout}'`;\n\nreturn {\n json: {\n cmd: cmd,\n containerId: containerId,\n containerName: containerName,\n action: action,\n chatId: chatId\n }\n};"
|
||||
},
|
||||
"id": "code-build-action-cmd",
|
||||
"name": "Build Action Command",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1780,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "={{ $json.cmd }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "exec-action",
|
||||
"name": "Execute Action",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2000,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse the HTTP status code from curl output\nconst stdout = $input.item.json.stdout;\nconst stderr = $input.item.json.stderr;\nconst actionData = $('Build Action Command').item.json;\nconst containerName = actionData.containerName;\nconst action = actionData.action;\nconst chatId = actionData.chatId;\n\n// Check for curl-level errors first (connection issues)\nif (stderr && stderr.trim()) {\n return {\n json: {\n success: false,\n chatId: chatId,\n text: `Failed to ${action} ${containerName}`\n }\n };\n}\n\nconst statusCode = parseInt(stdout.trim());\n\n// 204: Success, 304: Already in state (also success for user)\nif (statusCode === 204 || statusCode === 304) {\n const verb = action === 'start' ? 'started' :\n action === 'stop' ? 'stopped' : 'restarted';\n return {\n json: {\n success: true,\n chatId: chatId,\n text: `<b>${containerName}</b> ${verb} successfully`\n }\n };\n}\n\n// All error codes get terse message\nreturn {\n json: {\n success: false,\n chatId: chatId,\n text: `Failed to ${action} ${containerName}`\n }\n};"
|
||||
},
|
||||
"id": "code-parse-action-result",
|
||||
"name": "Parse Action Result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2220,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
@@ -1804,17 +1764,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1829,7 +1785,8 @@
|
||||
"fullResponse": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-get-container-for-action",
|
||||
"name": "Get Container For Action",
|
||||
@@ -1838,47 +1795,13 @@
|
||||
"position": [
|
||||
2000,
|
||||
1100
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Find container and execute action\nconst containers = $input.all().map(item => item.json);\nconst prevData = $('Prepare Immediate Action').item.json;\nconst containerName = prevData.containerName.toLowerCase();\nconst action = prevData.action;\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]) === containerName);\n\nif (!container) {\n return {\n json: {\n error: true,\n chatId,\n messageId,\n text: `Container \"${containerName}\" not found`\n }\n };\n}\n\nconst containerId = container.Id;\nconst timeout = action === 'restart' ? '?t=10' : '';\n\nreturn {\n json: {\n cmd: `curl -s -o /dev/null -w \"%{http_code}\" --max-time 15 -X POST 'http://docker-socket-proxy:2375/v1.47/containers/${containerId}/${action}${timeout}'`,\n containerId,\n containerName: normalizeName(container.Names[0]),\n action,\n chatId,\n messageId\n }\n};"
|
||||
},
|
||||
"id": "code-build-immediate-action-cmd",
|
||||
"name": "Build Immediate Action Command",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2220,
|
||||
1100
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "={{ $json.cmd }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "exec-immediate-action",
|
||||
"name": "Execute Immediate Action",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2440,
|
||||
1100
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build action completion message (success shows back only, error shows retry + back)\nconst stdout = $input.item.json.stdout;\nconst prevData = $('Build Immediate Action Command').item.json;\nconst containerName = prevData.containerName;\nconst action = prevData.action;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\n\nconst statusCode = parseInt(stdout.trim());\n\n// 204: Success, 304: Already in state (also success)\nconst success = statusCode === 204 || statusCode === 304;\n\n// Build message based on action type and result\nconst successMessages = {\n start: `\\u25B6\\uFE0F <b>${containerName}</b> started`,\n restart: `\\u{1F504} <b>${containerName}</b> restarted`\n};\n\nlet text;\nlet keyboard;\n\nif (success) {\n text = successMessages[action] || `Action completed on ${containerName}`;\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 text = `\\u274C Failed to ${action} <b>${containerName}</b>`;\n // Error: retry and back buttons\n keyboard = {\n inline_keyboard: [\n [{ text: '\\u{1F504} Try Again', callback_data: `action:${action}:${containerName}` }],\n [{ text: '\\u25C0\\uFE0F Back to Containers', callback_data: 'list:0' }]\n ]\n };\n}\n\nreturn {\n json: {\n chatId,\n messageId,\n text,\n reply_markup: keyboard\n }\n};"
|
||||
},
|
||||
"id": "code-format-immediate-result",
|
||||
"name": "Format Immediate Result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2660,
|
||||
1100
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -1945,17 +1868,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1970,7 +1889,8 @@
|
||||
"fullResponse": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-get-container-for-cancel",
|
||||
"name": "Get Container For Cancel",
|
||||
@@ -1979,11 +1899,17 @@
|
||||
"position": [
|
||||
1780,
|
||||
1800
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build submenu for return from cancel\nconst containers = $input.all().map(item => item.json);\nconst prevData = $input.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 From Confirm').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",
|
||||
@@ -2735,17 +2661,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2760,7 +2682,8 @@
|
||||
"fullResponse": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-get-all-containers-update-all",
|
||||
"name": "Get All Containers For Update All",
|
||||
@@ -2769,7 +2692,13 @@
|
||||
"position": [
|
||||
1200,
|
||||
2200
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -3052,17 +2981,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3077,7 +3002,8 @@
|
||||
"fullResponse": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-fetch-containers-update-all-exec",
|
||||
"name": "Fetch Containers For Update All Exec",
|
||||
@@ -3086,7 +3012,13 @@
|
||||
"position": [
|
||||
2200,
|
||||
2600
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -3184,17 +3116,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3209,7 +3137,8 @@
|
||||
"fullResponse": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-get-container-callback",
|
||||
"name": "Get Container For Callback Update",
|
||||
@@ -3218,7 +3147,13 @@
|
||||
"position": [
|
||||
2440,
|
||||
1650
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -4888,17 +4823,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -4913,7 +4844,8 @@
|
||||
"fullResponse": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-fetch-containers-bitmap-stop",
|
||||
"name": "Fetch Containers For Bitmap Stop",
|
||||
@@ -4922,7 +4854,13 @@
|
||||
"position": [
|
||||
2000,
|
||||
700
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -5035,115 +4973,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));\n"
|
||||
},
|
||||
"id": "code-graphql-normalizer",
|
||||
"name": "GraphQL Response Normalizer",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
200,
|
||||
2600
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "code-container-id-registry",
|
||||
"name": "Container ID Registry",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
200,
|
||||
2400
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "// Container ID Registry\n// Maps container names to Unraid PrefixedID format (129-char server_hash:container_hash)\n\n// Initialize registry using static data with JSON serialization pattern\n// CRITICAL: Must use top-level assignment per CLAUDE.md\nconst registry = $getWorkflowStaticData('global');\nif (!registry._containerIdMap) {\n registry._containerIdMap = JSON.stringify({});\n}\nif (!registry._lastRefresh) {\n registry._lastRefresh = 0;\n}\n\nconst containerMap = JSON.parse(registry._containerIdMap);\n\n/**\n * Update registry with fresh container data from Unraid GraphQL API\n * @param {Array} containers - Array of Unraid container objects {id, names[], state}\n * @returns {Object} Updated container map\n */\nfunction updateRegistry(containers) {\n const newMap = {};\n \n for (const container of containers) {\n // Extract container name: strip leading '/', lowercase\n const rawName = container.names[0];\n const name = rawName.startsWith('/') ? rawName.substring(1).toLowerCase() : rawName.toLowerCase();\n \n // Map name -> {name, unraidId}\n // The Unraid 'id' field IS the PrefixedID (129-char format)\n newMap[name] = {\n name: name,\n unraidId: container.id\n };\n }\n \n // Store timestamp\n registry._lastRefresh = Date.now();\n \n // Serialize (top-level assignment - this is what n8n persists)\n registry._containerIdMap = JSON.stringify(newMap);\n \n return newMap;\n}\n\n/**\n * Get Unraid PrefixedID for a container name\n * @param {string} containerName - Container name (e.g., 'plex', '/plex')\n * @returns {string} Unraid PrefixedID (129-char)\n * @throws {Error} If container not found\n */\nfunction getUnraidId(containerName) {\n // Normalize: strip leading '/', lowercase\n const name = containerName.startsWith('/') \n ? containerName.substring(1).toLowerCase() \n : containerName.toLowerCase();\n \n const entry = containerMap[name];\n \n if (!entry) {\n const registryAge = Date.now() - registry._lastRefresh;\n const isStale = registryAge > 60000; // 60 seconds\n \n if (isStale) {\n throw new Error(`Container \"${name}\" not found. Registry may be stale - try \"status\" to refresh.`);\n } else {\n throw new Error(`Container \"${name}\" not found. Check spelling or run \"status\" to see all containers.`);\n }\n }\n \n return entry.unraidId;\n}\n\n/**\n * Get full container entry by name\n * @param {string} containerName - Container name\n * @returns {Object} Container entry {name, unraidId}\n * @throws {Error} If container not found\n */\nfunction getContainerByName(containerName) {\n // Normalize name\n const name = containerName.startsWith('/') \n ? containerName.substring(1).toLowerCase() \n : containerName.toLowerCase();\n \n const entry = containerMap[name];\n \n if (!entry) {\n const registryAge = Date.now() - registry._lastRefresh;\n const isStale = registryAge > 60000; // 60 seconds\n \n if (isStale) {\n throw new Error(`Container \"${name}\" not found. Registry may be stale - try \"status\" to refresh.`);\n } else {\n throw new Error(`Container \"${name}\" not found. Check spelling or run \"status\" to see all containers.`);\n }\n }\n \n return entry;\n}\n\n// Detect mode based on input\nconst input = $input.item.json;\n\nif (input.containers && Array.isArray(input.containers)) {\n // Update mode: refresh registry with new container data\n const updatedMap = updateRegistry(input.containers);\n return {\n mode: 'update',\n registrySize: Object.keys(updatedMap).length,\n lastRefresh: new Date(registry._lastRefresh).toISOString(),\n containers: Object.keys(updatedMap)\n };\n} else if (input.containerName) {\n // Lookup mode: resolve container name to Unraid ID\n const unraidId = getUnraidId(input.containerName);\n const entry = getContainerByName(input.containerName);\n \n return {\n mode: 'lookup',\n containerName: entry.name,\n unraidId: unraidId\n };\n} else {\n throw new Error('Invalid input: provide either \"containers\" array or \"containerName\" string');\n}\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// GraphQL Error Handler - Standardized error checking and HTTP status mapping\n// Input: $input.item.json = raw response from HTTP Request node\n// Output: { success, statusCode, alreadyInState, message, data }\n\nconst response = $input.item.json;\n\n// Check GraphQL errors array\nif (response.errors && response.errors.length > 0) {\n const error = response.errors[0];\n const code = error.extensions?.code;\n const message = error.message;\n \n // Map error codes to HTTP equivalents\n if (code === 'ALREADY_IN_STATE') {\n // Maps to Docker API HTTP 304 pattern (used in n8n-actions.json)\n return {\n json: {\n success: true,\n statusCode: 304,\n alreadyInState: true,\n message: 'Container already in desired state'\n }\n };\n }\n \n // Error codes that should throw\n if (code === 'NOT_FOUND') {\n throw new Error(`Container not found: ${message}`);\n }\n \n if (code === 'FORBIDDEN' || code === 'UNAUTHORIZED') {\n throw new Error(`Permission denied: ${message}. Check API key permissions.`);\n }\n \n // Any other GraphQL error\n throw new Error(`Unraid API error: ${message}`);\n}\n\n// Check HTTP-level errors\nif (response.statusCode >= 400) {\n throw new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`);\n}\n\n// Check missing data field\nif (!response.data) {\n throw new Error('GraphQL response missing data field');\n}\n\n// Success\nreturn {\n json: {\n success: true,\n statusCode: 200,\n alreadyInState: false,\n data: response.data\n }\n};\n"
|
||||
},
|
||||
"id": "code-graphql-error-handler",
|
||||
"name": "GraphQL Error Handler",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
600,
|
||||
2600
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\"query\": \"query { docker { containers { id names state } } }\"}",
|
||||
"options": {
|
||||
"timeout": 15000,
|
||||
"allowUnauthorizedCerts": true,
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "http-unraid-api-template",
|
||||
"name": "Unraid API HTTP Template",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1000,
|
||||
2600
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "// Callback Token Encoder\n// Compresses 129-char Unraid PrefixedID to 8-char token for Telegram callback_data\n\n// Initialize token store using static data with JSON serialization\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData._callbackTokens) {\n staticData._callbackTokens = JSON.stringify({});\n}\nconst tokenStore = JSON.parse(staticData._callbackTokens);\n\n/**\n * Encode Unraid PrefixedID to 8-character token\n * Uses SHA-256 with collision detection\n * @param {string} unraidId - 129-char PrefixedID (server_hash:container_hash)\n * @returns {string} 8-character hex token\n */\nasync function encodeToken(unraidId) {\n // Generate SHA-256 hash\n const encoder = new TextEncoder();\n const data = encoder.encode(unraidId);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n \n // Try up to 7 non-overlapping 8-char windows (56 chars of SHA-256)\n for (let offset = 0; offset < 56; offset += 8) {\n const token = hashHex.substring(offset, offset + 8);\n \n // Check for collision\n if (tokenStore[token]) {\n // If same unraidId, reuse token (idempotent)\n if (tokenStore[token] === unraidId) {\n return token;\n }\n // Collision with different ID - try next window\n continue;\n }\n \n // No collision - store and return\n tokenStore[token] = unraidId;\n staticData._callbackTokens = JSON.stringify(tokenStore);\n return token;\n }\n \n // All 7 windows collided (extremely unlikely)\n throw new Error(`Token collision: all 7 hash windows collided for ${unraidId.substring(0, 20)}...`);\n}\n\n// Process input\nconst input = $input.item.json;\nconst unraidId = input.unraidId;\n\nif (!unraidId) {\n throw new Error('Missing required input: unraidId');\n}\n\n// Encode the token\nconst token = await encodeToken(unraidId);\n\n// Build callback data if action provided\nlet callbackData = null;\nlet byteSize = null;\nlet warning = null;\n\nif (input.action) {\n callbackData = `action:${input.action}:${token}`;\n byteSize = new TextEncoder().encode(callbackData).length;\n \n if (byteSize > 64) {\n warning = `Callback data exceeds 64-byte limit: ${byteSize} bytes`;\n }\n}\n\nreturn {\n token,\n unraidId,\n callbackData,\n byteSize,\n warning\n};\n"
|
||||
},
|
||||
"id": "code-callback-token-encoder",
|
||||
"name": "Callback Token Encoder",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
600,
|
||||
2400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "// Callback Token Decoder\n// Resolves 8-char token back to 129-char Unraid PrefixedID\n\n// Load token store from static data\nconst staticData = $getWorkflowStaticData('global');\nconst tokenStore = JSON.parse(staticData._callbackTokens || '{}');\n\n/**\n * Decode token to Unraid PrefixedID\n * @param {string} token - 8-character hex token\n * @returns {string} Unraid PrefixedID (129-char)\n * @throws {Error} If token not found\n */\nfunction decodeToken(token) {\n const unraidId = tokenStore[token];\n \n if (!unraidId) {\n throw new Error(`Token not found: ${token}. Token store may have been cleared.`);\n }\n \n return unraidId;\n}\n\n// Process input\nconst input = $input.item.json;\nlet token = input.token;\nlet action = null;\n\n// If callbackData provided, parse it\nif (!token && input.callbackData) {\n const parts = input.callbackData.split(':');\n \n // Expected format: \"action:start:a1b2c3d4\" or \"action:stop:a1b2c3d4\"\n if (parts.length >= 3) {\n action = parts[1]; // e.g., \"start\", \"stop\"\n token = parts[parts.length - 1]; // Last segment is token\n } else {\n throw new Error(`Invalid callbackData format: ${input.callbackData}. Expected \"action:<action>:<token>\"`);\n }\n}\n\nif (!token) {\n throw new Error('Missing required input: token or callbackData');\n}\n\n// Decode the token\nconst unraidId = decodeToken(token);\n\nreturn {\n token,\n unraidId,\n action\n};\n"
|
||||
},
|
||||
"id": "code-callback-token-decoder",
|
||||
"name": "Callback Token Decoder",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1000,
|
||||
2400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// GraphQL Response Normalizer - Transform Unraid API to Docker API contract\nconst response = $input.item.json;\n\n// Validate GraphQL response\nif (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n}\n\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure');\n}\n\nconst containers = response.data.docker.containers;\n\n// Transform to Docker API format\nconst normalized = containers.map(container => {\n // State mapping: RUNNING\u2192running, STOPPED\u2192exited, PAUSED\u2192paused\n const stateLower = container.state ? container.state.toLowerCase() : 'unknown';\n \n return {\n Id: container.id, // Full 129-char PrefixedID\n Names: container.names || [container.name ? `/${container.name}` : '/unknown'],\n State: stateLower === 'stopped' ? 'exited' : stateLower,\n Status: container.status || stateLower,\n Image: container.image || '',\n ImageId: container.imageId || ''\n };\n});\n\nreturn normalized.map(container => ({ json: container }));\n"
|
||||
@@ -5348,17 +5177,13 @@
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||
"authentication": "none",
|
||||
"authentication": "genericCredentialType",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "x-api-key",
|
||||
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -5373,16 +5198,23 @@
|
||||
"fullResponse": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"genericAuthType": "httpHeaderAuth"
|
||||
},
|
||||
"id": "http-execute-batch-update",
|
||||
"name": "Execute Batch Update",
|
||||
"name": "Execute Batch Mutation",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3000,
|
||||
2500
|
||||
]
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "unraid-api-key-credential-id",
|
||||
"name": "Unraid API Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -5695,39 +5527,6 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Action Command": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Action",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Action": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Action Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Action Result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Action Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Batch Keyboard": {
|
||||
"main": [
|
||||
[
|
||||
@@ -6085,40 +5884,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Inline Action Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Immediate Action Command": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Immediate Action",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Immediate Action": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Immediate Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Immediate Result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Immediate Result",
|
||||
"node": "Normalize GraphQL Response (Action)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -6140,7 +5906,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Cancel Return Submenu",
|
||||
"node": "Normalize GraphQL Response (Cancel)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -6173,7 +5939,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Batch Loop",
|
||||
"node": "Check Batch Size",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -6311,7 +6077,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Available Updates",
|
||||
"node": "Normalize GraphQL Response (Update All)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -6435,7 +6201,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Update All Batch",
|
||||
"node": "Normalize GraphQL Response (Update All Exec)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -6490,7 +6256,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Find Container For Callback Update",
|
||||
"node": "Normalize GraphQL Response (Callback)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -7283,7 +7049,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Resolve Batch Stop Names",
|
||||
"node": "Normalize GraphQL Response (Bitmap Stop)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -7334,211 +7100,194 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-get-container-for-action": {
|
||||
"Normalize GraphQL Response (Action)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "normalize-graphql-action",
|
||||
"node": "Update Container Registry (Action)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"normalize-graphql-action": {
|
||||
"Update Container Registry (Action)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "registry-update-action",
|
||||
"node": "Prepare Inline Action Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"registry-update-action": {},
|
||||
"http-get-container-for-cancel": {
|
||||
"Normalize GraphQL Response (Cancel)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "normalize-graphql-cancel",
|
||||
"node": "Update Container Registry (Cancel)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"normalize-graphql-cancel": {
|
||||
"Update Container Registry (Cancel)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "registry-update-cancel",
|
||||
"node": "Build Cancel Return Submenu",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"registry-update-cancel": {},
|
||||
"http-get-all-containers-update-all": {
|
||||
"Normalize GraphQL Response (Update All)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "normalize-graphql-update-all",
|
||||
"node": "Update Container Registry (Update All)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"normalize-graphql-update-all": {
|
||||
"Update Container Registry (Update All)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "registry-update-update-all",
|
||||
"node": "Check Available Updates",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"registry-update-update-all": {},
|
||||
"http-fetch-containers-update-all-exec": {
|
||||
"Normalize GraphQL Response (Update All Exec)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "normalize-graphql-update-all-exec",
|
||||
"node": "Update Container Registry (Update All Exec)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"normalize-graphql-update-all-exec": {
|
||||
"Update Container Registry (Update All Exec)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "registry-update-update-all-exec",
|
||||
"node": "Prepare Update All Batch",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"registry-update-update-all-exec": {},
|
||||
"http-get-container-callback": {
|
||||
"Normalize GraphQL Response (Callback)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "normalize-graphql-callback",
|
||||
"node": "Update Container Registry (Callback)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"normalize-graphql-callback": {
|
||||
"Update Container Registry (Callback)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "registry-update-callback",
|
||||
"node": "Find Container For Callback Update",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"registry-update-callback": {},
|
||||
"http-fetch-containers-bitmap-stop": {
|
||||
"Normalize GraphQL Response (Bitmap Stop)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "normalize-graphql-bitmap-stop",
|
||||
"node": "Update Container Registry (Bitmap Stop)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"normalize-graphql-bitmap-stop": {
|
||||
"Update Container Registry (Bitmap Stop)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "registry-update-bitmap-stop",
|
||||
"node": "Resolve Batch Stop Names",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"registry-update-bitmap-stop": {},
|
||||
"code-prepare-update-all-batch": {
|
||||
"Check Batch Size": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "if-check-batch-size",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"if-check-batch-size": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "code-build-batch-update-mutation",
|
||||
"node": "Build Batch Update Mutation",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "code-prepare-batch-loop",
|
||||
"node": "Prepare Batch Loop",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-build-batch-update-mutation": {
|
||||
"Build Batch Update Mutation": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "http-execute-batch-update",
|
||||
"node": "Execute Batch Mutation",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-execute-batch-update": {
|
||||
"Handle Batch Update Response": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "code-handle-batch-update-response",
|
||||
"node": "Format Batch Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-handle-batch-update-response": {
|
||||
"Format Batch Result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "code-format-batch-result",
|
||||
"node": "Send Batch Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"code-format-batch-result": {
|
||||
"Execute Batch Mutation": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "telegram-send-batch-result",
|
||||
"node": "Handle Batch Update Response",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -7554,4 +7303,4 @@
|
||||
"tags": [],
|
||||
"triggerCount": 1,
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user