Files
docker/monitoring/node-red/data/flows.json
T
git d519139615 new file: monitoring/node-red/data/.config.nodes.json
new file:   monitoring/node-red/data/.config.runtime.json
	new file:   monitoring/node-red/data/.config.runtime.json.backup
	new file:   monitoring/node-red/data/.config.users.json
	new file:   monitoring/node-red/data/.config.users.json.backup
	new file:   monitoring/node-red/data/.flows.json.backup
	new file:   monitoring/node-red/data/.flows_cred.json.backup
	new file:   monitoring/node-red/data/.npm/_cacache/content-v2/sha512/b0/47/c1458664fa9b6a08e9035110b523127a96bd7285d19472dc702f5dc498b927412b0ecd3273708fbf9d61754520599ac0b0e11f3e4c4d4ac784e78d7d97fe
	new file:   monitoring/node-red/data/.npm/_cacache/content-v2/sha512/c2/c2/b64870ea5c5a42b5772106f51123cf684d3c8381de10ccc07d01168d111d0a1ab79ee26fda320b3027c76cfc856119f7b440845a83c9f22d7d731643e62f
	new file:   monitoring/node-red/data/.npm/_cacache/index-v5/15/a4/2638498d877ec2c8c3d88cb9c08d7867c52d3fceb6fc64cc5abde73b01a9
	new file:   monitoring/node-red/data/.npm/_cacache/index-v5/48/03/b8903b717bbc1ad41b3f37148db48f54e0828c1aef870973f6672895d689
	new file:   monitoring/node-red/data/.npm/_logs/2026-04-05T01_36_15_515Z-debug-0.log
	new file:   monitoring/node-red/data/context/00b02bbd01c91485/flow.json
	new file:   monitoring/node-red/data/context/global/global.json
	new file:   monitoring/node-red/data/flows.json
	new file:   monitoring/node-red/data/flows_cred.json
	new file:   monitoring/node-red/data/node_modules/.package-lock.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/CHANGELOG.md
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/LICENSE
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/README.md
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/flow-debugger.html
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/flow-debugger.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/flow-debugger.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/MessageQueue.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/MessageQueue.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/debugger.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/debugger.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/location.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/location.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/types.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/lib/types.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/locales/en-US/flow-debugger.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/locales/ja/flow-debugger.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/nr-types.js
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/dist/nr-types.js.map
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/package.json
	new file:   monitoring/node-red/data/node_modules/node-red-debugger/resources/style.css
	new file:   monitoring/node-red/data/package-lock.json
	new file:   monitoring/node-red/data/package.json
	new file:   monitoring/node-red/data/settings.js
	new file:   monitoring/node-red/data/test-container.sh
	new file:   monitoring/node-red/data/test-container.sh.old
	new file:   monitoring/node-red/data/webhook.json
2026-04-13 05:36:20 +10:00

1122 lines
41 KiB
JSON

[
{
"id": "00b02bbd01c91485",
"type": "tab",
"label": "Grafana Docker Safe Update",
"disabled": false,
"info": ""
},
{
"id": "f16653fb4cb05bbe",
"type": "tab",
"label": "Health Check",
"disabled": false,
"info": "",
"env": []
},
{
"id": "d0f45d2b6f117365",
"type": "tab",
"label": "uptime-kuma alerts",
"disabled": false,
"info": "",
"env": []
},
{
"id": "c5240b64a962ea54",
"type": "tab",
"label": "Gotify Messages",
"disabled": false,
"info": "",
"env": []
},
{
"id": "22f648a14d2e8fae",
"type": "inject",
"z": "00b02bbd01c91485",
"name": "Test Alert",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": "",
"topic": "",
"payload": "{\"receiver\":\"node-red\",\"status\":\"firing\",\"alerts\":[{\"status\":\"firing\",\"labels\":{\"Active\":\"telegraf: 0 updates\",\"Restored\":\"telegraf: 0 updates\",\"alertname\":\"Updates\",\"com_docker_compose_project\":\"core\",\"compose_image\":\"telegraf:latest\",\"container\":\"telegraf\",\"grafana_folder\":\"Infrastructure\",\"job\":\"container-updates\",\"running_image\":\"telegraf:latest\"},\"annotations\":{},\"startsAt\":\"2026-03-31T01:16:10Z\",\"endsAt\":\"0001-01-01T00:00:00Z\",\"generatorURL\":\"https://grafana.lan.ddnsgeek.com/alerting/grafana/bfgysfo3iregwf/view?orgId=1\",\"fingerprint\":\"49b1e1dbf1255191\",\"silenceURL\":\"https://grafana.lan.ddnsgeek.com/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3Dbfgysfo3iregwf&matcher=Active%3Dtelegraf%3A+0+updates&matcher=Restored%3Dtelegraf%3A+0+updates&matcher=com_docker_compose_project%3Dcore&matcher=compose_image%3Dtelegraf%3Alatest&matcher=container%3Dtelegraf&matcher=job%3Dcontainer-updates&matcher=running_image%3Dtelegraf%3Alatest&orgId=1\",\"dashboardURL\":\"https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916170000&orgId=1&to=1774926701171\",\"panelURL\":\"https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916170000&orgId=1&to=1774926701171&viewPanel=6\",\"ruleUID\":\"bfgysfo3iregwf\",\"values\":{\"A\":1,\"D\":1},\"valueString\":\"[ var='A' labels={com_docker_compose_project=core, compose_image=telegraf:latest, container=telegraf, job=container-updates, running_image=telegraf:latest} type='query' value=1 ], [ var='D' labels={com_docker_compose_project=core, compose_image=telegraf:latest, container=telegraf, job=container-updates, running_image=telegraf:latest} type='threshold' value=1 ]\",\"orgId\":1},{\"status\":\"firing\",\"labels\":{\"Active\":\"update-test: 0 updates\",\"Restored\":\"update-test: 0 updates\",\"alertname\":\"Updates\",\"com_docker_compose_project\":\"core\",\"compose_image\":\"nginx:1.27.1\",\"container\":\"update-test\",\"grafana_folder\":\"Infrastructure\",\"job\":\"container-updates\",\"running_image\":\"nginx:1.27.0\"},\"annotations\":{},\"startsAt\":\"2026-03-31T01:15:10Z\",\"endsAt\":\"0001-01-01T00:00:00Z\",\"generatorURL\":\"https://grafana.lan.ddnsgeek.com/alerting/grafana/bfgysfo3iregwf/view?orgId=1\",\"fingerprint\":\"c56bd9f28943cfac\",\"silenceURL\":\"https://grafana.lan.ddnsgeek.com/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3Dbfgysfo3iregwf&matcher=Active%3Dupdate-test%3A+0+updates&matcher=Restored%3Dupdate-test%3A+0+updates&matcher=com_docker_compose_project%3Dcore&matcher=compose_image%3Dnginx%3A1.27.1&matcher=container%3Dupdate-test&matcher=job%3Dcontainer-updates&matcher=running_image%3Dnginx%3A1.27.0&orgId=1\",\"dashboardURL\":\"https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916110000&orgId=1&to=1774926701171\",\"panelURL\":\"https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916110000&orgId=1&to=1774926701171&viewPanel=6\",\"ruleUID\":\"bfgysfo3iregwf\",\"values\":{\"A\":1,\"D\":1},\"valueString\":\"[ var='A' labels={com_docker_compose_project=core, compose_image=nginx:1.27.1, container=update-test, job=container-updates, running_image=nginx:1.27.0} type='query' value=1 ], [ var='D' labels={com_docker_compose_project=core, compose_image=nginx:1.27.1, container=update-test, job=container-updates, running_image=nginx:1.27.0} type='threshold' value=1 ]\",\"orgId\":1}],\"groupLabels\":{\"alertname\":\"Updates\",\"grafana_folder\":\"Infrastructure\"},\"commonLabels\":{\"alertname\":\"Updates\",\"com_docker_compose_project\":\"core\",\"grafana_folder\":\"Infrastructure\",\"job\":\"container-updates\"},\"commonAnnotations\":{},\"externalURL\":\"https://grafana.lan.ddnsgeek.com/\",\"appVersion\":\"12.4.2\",\"version\":\"1\",\"groupKey\":\"{}/{__grafana_autogenerated__=\\\"true\\\"}/{__grafana_receiver__=\\\"node-red\\\"}/{__grafana_route_settings_hash__=\\\"f177b219cbbdd2e2\\\"}:{alertname=\\\"Updates\\\", grafana_folder=\\\"Infrastructure\\\"}\",\"truncatedAlerts\":0,\"orgId\":1,\"title\":\"[FIRING:2] Updates Infrastructure (core container-updates)\",\"state\":\"alerting\",\"message\":\"**Firing**\\n\\nValue: A=1, D=1\\nLabels:\\n - alertname = Updates\\n - Active = telegraf: 0 updates\\n - Restored = telegraf: 0 updates\\n - com_docker_compose_project = core\\n - compose_image = telegraf:latest\\n - container = telegraf\\n - grafana_folder = Infrastructure\\n - job = container-updates\\n - running_image = telegraf:latest\\nAnnotations:\\nSource: https://grafana.lan.ddnsgeek.com/alerting/grafana/bfgysfo3iregwf/view?orgId=1\\nSilence: https://grafana.lan.ddnsgeek.com/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3Dbfgysfo3iregwf&matcher=Active%3Dtelegraf%3A+0+updates&matcher=Restored%3Dtelegraf%3A+0+updates&matcher=com_docker_compose_project%3Dcore&matcher=compose_image%3Dtelegraf%3Alatest&matcher=container%3Dtelegraf&matcher=job%3Dcontainer-updates&matcher=running_image%3Dtelegraf%3Alatest&orgId=1\\nDashboard: https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916170000&orgId=1&to=1774926701171\\nPanel: https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916170000&orgId=1&to=1774926701171&viewPanel=6\\n\\nValue: A=1, D=1\\nLabels:\\n - alertname = Updates\\n - Active = update-test: 0 updates\\n - Restored = update-test: 0 updates\\n - com_docker_compose_project = core\\n - compose_image = nginx:1.27.1\\n - container = update-test\\n - grafana_folder = Infrastructure\\n - job = container-updates\\n - running_image = nginx:1.27.0\\nAnnotations:\\nSource: https://grafana.lan.ddnsgeek.com/alerting/grafana/bfgysfo3iregwf/view?orgId=1\\nSilence: https://grafana.lan.ddnsgeek.com/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3Dbfgysfo3iregwf&matcher=Active%3Dupdate-test%3A+0+updates&matcher=Restored%3Dupdate-test%3A+0+updates&matcher=com_docker_compose_project%3Dcore&matcher=compose_image%3Dnginx%3A1.27.1&matcher=container%3Dupdate-test&matcher=job%3Dcontainer-updates&matcher=running_image%3Dnginx%3A1.27.0&orgId=1\\nDashboard: https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916110000&orgId=1&to=1774926701171\\nPanel: https://grafana.lan.ddnsgeek.com/d/ad895wr?from=1774916110000&orgId=1&to=1774926701171&viewPanel=6\\n\"}",
"payloadType": "json",
"x": 140,
"y": 100,
"wires": [
[
"671243b648c669fe"
]
]
},
{
"id": "17eef927bdc12e54",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Create Pull Command",
"func": "const labels = msg.payload.alerts.labels || {};\nconst container = labels.container;\nconst image = labels.compose_image || labels.running_image || labels.image;\nconst project = labels.com_docker_compose_project\n\nif (project == \"core\") {\n var host = \"docker\"\n}\nelse {\n host = \"raspi\"\n}\n\nif (!container) {\n node.warn(\"No container found in alert labels\");\n return null; // skip this alert\n}\n\nmsg.payload = `/compose/${host}/services-up.sh --profile all pull -q ${container}`;\nmsg.container = container;\nmsg.image = image;\nmsg.host = host;\nnode.log(`New docker update available\n Container: ${container}\n Image: ${image}\n Host: ${host}`)\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1300,
"y": 80,
"wires": [
[
"e92429c3061966f7"
]
]
},
{
"id": "e92429c3061966f7",
"type": "exec",
"z": "00b02bbd01c91485",
"command": "",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "Pull Image",
"x": 1510,
"y": 80,
"wires": [
[
"5c36c5b0e44d7cb0"
],
[
"5c36c5b0e44d7cb0"
],
[
"3af6aa9cbaad6ff9"
]
]
},
{
"id": "b8a933687445fb13",
"type": "exec",
"z": "00b02bbd01c91485",
"command": "",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "70",
"winHide": false,
"oldrc": true,
"name": "Test New Image",
"x": 2160,
"y": 80,
"wires": [
[
"40e040d2b58bbaa1",
"4c182d7a643805cd"
],
[
"4c182d7a643805cd"
],
[]
]
},
{
"id": "40e040d2b58bbaa1",
"type": "switch",
"z": "00b02bbd01c91485",
"name": "Check Test Result",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "cont",
"v": "0",
"vt": "str"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 2370,
"y": 80,
"wires": [
[
"da5d95a30589805c"
],
[
"47ac22574893de41"
]
]
},
{
"id": "ab2063f9180cd1aa",
"type": "exec",
"z": "00b02bbd01c91485",
"command": "",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": true,
"name": "Deploy Production Container",
"x": 2960,
"y": 80,
"wires": [
[
"32e3684b63a6f13e"
],
[
"e8e4151333935b78",
"32e3684b63a6f13e"
],
[]
]
},
{
"id": "a4c73e8e3e87fab7",
"type": "http in",
"z": "00b02bbd01c91485",
"name": "Grafana Webhook",
"url": "/grafana-update",
"method": "post",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 110,
"y": 60,
"wires": [
[
"671243b648c669fe"
]
]
},
{
"id": "3af6aa9cbaad6ff9",
"type": "switch",
"z": "00b02bbd01c91485",
"name": "image pulled OK?",
"property": "payload.code",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "0",
"vt": "num"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 1710,
"y": 80,
"wires": [
[
"96c81501330c20e3"
],
[
"1de1d1befec88aa4"
]
]
},
{
"id": "96c81501330c20e3",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Build Test Command",
"func": "const container = msg.container;\nconst image = msg.image;\nconst host = msg.host;\nmsg.payload = `/data/test-container.sh ${container} ${host}`;\nmsg.image = image;\nmsg.container = container;\nmsg.host = host\nnode.log(`Pull Successful\n Container: ${container}\n Image: ${msg.image}\n Host: ${host}`\n )\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1940,
"y": 80,
"wires": [
[
"b8a933687445fb13"
]
]
},
{
"id": "95e72a493b5b736c",
"type": "split",
"z": "00b02bbd01c91485",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": true,
"addname": "",
"property": "payload.alerts",
"x": 610,
"y": 80,
"wires": [
[
"298528baa0f776b4"
]
]
},
{
"id": "da5d95a30589805c",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Build Deploy Command",
"func": "const container = msg.container;\nconst image = msg.image;\nconst host = msg.host;\nmsg.payload = `/compose/${host}/services-up.sh --profile all up -d ${container}`;\nmsg.image = image;\nmsg.container = container;\nmsg.host = host;\nnode.log(`Test Successful\n Container: ${container}\n Image: ${msg.image}\n Host: ${host}`\n )\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2670,
"y": 80,
"wires": [
[
"ab2063f9180cd1aa"
]
]
},
{
"id": "27ac6d1d2b208f99",
"type": "link out",
"z": "00b02bbd01c91485",
"name": "Docker image pull fail",
"mode": "link",
"links": [
"ba2c4e239d3b1e1d"
],
"x": 2095,
"y": 140,
"wires": []
},
{
"id": "49c52031d95c0638",
"type": "link out",
"z": "00b02bbd01c91485",
"name": "Docker image test fail",
"mode": "link",
"links": [
"10718f4768834442"
],
"x": 2795,
"y": 140,
"wires": []
},
{
"id": "b083fcf71669254e",
"type": "link out",
"z": "00b02bbd01c91485",
"name": "Docker update success",
"mode": "link",
"links": [
"f5771c9cc8e81779"
],
"x": 3415,
"y": 80,
"wires": []
},
{
"id": "671243b648c669fe",
"type": "function",
"z": "00b02bbd01c91485",
"name": "WEBHOOK /grafana-update IN",
"func": "node.log(\n \"Payload:\\n\" +\n JSON.stringify(msg.payload, null, 2)\n);\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 390,
"y": 80,
"wires": [
[
"95e72a493b5b736c"
]
]
},
{
"id": "e8e4151333935b78",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Deployment Success",
"func": "const attempts = flow.get(\"dockerUpdateAttempts\") || {};\n\nif (msg.updateKey && attempts[msg.updateKey]) {\n delete attempts[msg.updateKey];\n flow.set(\"dockerUpdateAttempts\", attempts);\n}\nnode.log(`Deployment Successful\n Container: ${msg.container}\n Image: ${msg.image}\n Host: ${msg.host}\n`);\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3240,
"y": 80,
"wires": [
[
"b083fcf71669254e"
]
]
},
{
"id": "1de1d1befec88aa4",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Pull Image Failed",
"func": "const labels = msg.payload.alerts.labels || {};\nconst container = labels.container;\nconst host = msg.host\nconst image = labels.compose_image || labels.running_image || labels.image;\n\nlet attempts = flow.get(\"dockerUpdateAttempts\") || {};\n\nif (msg.updateKey && attempts[msg.updateKey]) {\n const firstFailure = !attempts[msg.updateKey].notified;\n\n attempts[msg.updateKey].status = \"test_failed\";\n attempts[msg.updateKey].failedAt = Date.now();\n\n if (firstFailure) {\n attempts[msg.updateKey].notified = true;\n\n msg.notification = {\n title: `Docker update locked out: ${msg.container}`,\n message:\n `Automatic update for ${msg.container} failed.\\n\n Image: ${msg.image}\n Host: ${msg.host}\n Result: ${attempts[msg.updateKey].status}\\n\n Further Grafana alerts for this update will be ignored until manual intervention.`\n };\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n\n // send to 2 outputs:\n // output 1 = existing failure handling\n // output 2 = notification flow\n return [msg, msg];\n }\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n}\n\nnode.log(`Pull image failed\n Command: ${msg.payload}\n Container: ${container}\n Image: ${image}\n Host: ${host}`)\nreturn [msg, null];",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1930,
"y": 140,
"wires": [
[
"27ac6d1d2b208f99"
],
[
"0e6520dfb81a951b"
]
]
},
{
"id": "47ac22574893de41",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Test Image Failed",
"func": "//const labels = msg.payload.alerts.labels || {};\n//const container = labels.container;\nconst host = msg.host;\n//const image = labels.compose_image || labels.running_image || labels.image;\n\nlet attempts = flow.get(\"dockerUpdateAttempts\") || {};\n\nif (msg.updateKey && attempts[msg.updateKey]) {\n const firstFailure = !attempts[msg.updateKey].notified;\n\n attempts[msg.updateKey].status = \"test_failed\";\n attempts[msg.updateKey].failedAt = Date.now();\n\n if (firstFailure) {\n attempts[msg.updateKey].notified = true;\n\n msg.notification = {\n title: `Docker update locked out: ${msg.container}`,\n message:\n `Automatic update for ${msg.container} failed.\\n\n Image: ${msg.image}\n Host: ${msg.host}\n Result: ${attempts[msg.updateKey].status}\\n\n Further Grafana alerts for this update will be ignored until manual intervention.`\n };\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n\n // send to 2 outputs:\n // output 1 = existing failure handling\n // output 2 = notification flow\n return [msg, msg];\n }\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n}\n\nnode.log(`Test image failed\\n\n Command: ${msg.payload}\\n\n Container: ${msg.container}\\n\n Image: ${msg.image}\\n\n Host: ${host}`)\nreturn [msg, null];",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2650,
"y": 140,
"wires": [
[
"49c52031d95c0638"
],
[
"77675958f77d2d5f"
]
]
},
{
"id": "298528baa0f776b4",
"type": "switch",
"z": "00b02bbd01c91485",
"name": "docker project name",
"property": "payload.alerts.labels.com_docker_compose_project",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "core",
"vt": "str"
},
{
"t": "eq",
"v": "raspi",
"vt": "str"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 3,
"x": 800,
"y": 80,
"wires": [
[
"bad4d106425b7dd2"
],
[
"bad4d106425b7dd2"
],
[
"1ab4069c08a68fa3"
]
]
},
{
"id": "1ab4069c08a68fa3",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Unknown Project",
"func": "const labels = msg.payload.alerts.labels || {};\nconst container = labels.container;\nconst image = labels.compose_image || labels.running_image || labels.image;\nconst project = labels.com_docker_compose_project\n\nnode.warn(`Unable to map project name ${project} to host.\\n\n Updates for ${container} failed`)\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1290,
"y": 200,
"wires": [
[
"852d039f540dd0d3"
]
]
},
{
"id": "852d039f540dd0d3",
"type": "link out",
"z": "00b02bbd01c91485",
"name": "Docker updates fail unknown",
"mode": "link",
"links": [
"7c62509d505c281b"
],
"x": 1455,
"y": 200,
"wires": []
},
{
"id": "f4b91e278109e661",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Repeat update Suppresed",
"func": "node.warn(\n `${msg.payload.alerts.labels.container} ` +\n `(${msg.payload.alerts.labels.compose_image})`\n);\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1310,
"y": 140,
"wires": [
[]
]
},
{
"id": "5c36c5b0e44d7cb0",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Logging",
"func": "//const labels = msg.payload.alerts.labels || {};\n//const container = labels.container;\n//const image = labels.compose_image || labels.running_image || labels.image;\n\nif (msg.payload != \"\") {\n node.log(msg.payload)\n}\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1680,
"y": 40,
"wires": [
[]
]
},
{
"id": "4c182d7a643805cd",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Logging",
"func": "//const labels = msg.payload.alerts.labels || {};\n//const container = labels.container;\n//const image = labels.compose_image || labels.running_image || labels.image;\nif (msg.payload != \"\") {\n node.log(msg.payload)\n}\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2340,
"y": 40,
"wires": [
[]
]
},
{
"id": "32e3684b63a6f13e",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Logging",
"func": "//const labels = msg.payload.alerts.labels || {};\n//const container = labels.container;\n//const image = labels.compose_image || labels.running_image || labels.image;\nif (msg.payload != \"\") {\n node.log(msg.payload)\n}\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 3200,
"y": 40,
"wires": [
[]
]
},
{
"id": "bad4d106425b7dd2",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Check Already Attempted",
"func": "const labels = msg.payload.alerts.labels || {};\n\nconst container = labels.container;\nconst image = labels.compose_image || labels.running_image || labels.image;\nconst project = labels.com_docker_compose_project\n\n// Load persistent attempt registry\nlet attempts = flow.get(\"dockerUpdateAttempts\") || {};\n\nif (project == \"core\") {\n var host = \"docker\"\n}\nelse {\n host = \"raspi\"\n}\n\n// Unique key for this exact update attempt\nconst key = `${container}|${image}|${host}`;\n\n// If we've already tried this image for this container, suppress it\nif (attempts[key]) {\n node.warn(\n `Ignoring repeated update alert for ${container} -> ${image}. ` +\n `Already attempted at ${new Date(attempts[key].time).toISOString()}`\n );\n\n msg.suppressed = true;\n msg.updateKey = key;\n msg.attemptInfo = attempts[key];\n\n // output 1 = continue flow\n // output 2 = suppressed repeat\n return [null, msg];\n}\n\n// First time we've seen this update, record it immediately\nattempts[key] = {\n time: Date.now(),\n status: \"started\"\n};\n\nflow.set(\"dockerUpdateAttempts\", attempts);\n\nmsg.updateKey = key;\n\nnode.log(`First attempt for ${container} -> ${image}`);\n\n// continue normal flow\nreturn [msg, null];",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1050,
"y": 80,
"wires": [
[
"17eef927bdc12e54"
],
[
"f4b91e278109e661"
]
]
},
{
"id": "0e6520dfb81a951b",
"type": "link out",
"z": "00b02bbd01c91485",
"name": "Docker image update locked",
"mode": "link",
"links": [
"b72f58c8b6d6d265"
],
"x": 2095,
"y": 200,
"wires": []
},
{
"id": "77675958f77d2d5f",
"type": "link out",
"z": "00b02bbd01c91485",
"name": "Docker image update locked",
"mode": "link",
"links": [
"b72f58c8b6d6d265"
],
"x": 2795,
"y": 200,
"wires": []
},
{
"id": "8c74f9646c655f07",
"type": "http in",
"z": "00b02bbd01c91485",
"name": "/docker-update-lockouts in",
"url": "/docker-update-lockouts",
"method": "get",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 250,
"y": 340,
"wires": [
[
"bf13590833ea0288"
]
]
},
{
"id": "bf13590833ea0288",
"type": "function",
"z": "00b02bbd01c91485",
"name": "get docker update lockouts",
"func": "const attempts = flow.get(\"dockerUpdateAttempts\") || {};\n\nconst output = Object.entries(attempts).map(([key, value]) => ({\n key,\n status: value.status,\n firstAttempt: new Date(value.time).toISOString(),\n failedAt: value.failedAt\n ? new Date(value.failedAt).toISOString()\n : null,\n notified: !!value.notified\n}));\n\nmsg.payload = output;\nnode.log(JSON.stringify(msg.payload, null, 2));\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 540,
"y": 340,
"wires": [
[
"2fc3cf8f165b7537"
]
]
},
{
"id": "2fc3cf8f165b7537",
"type": "http response",
"z": "00b02bbd01c91485",
"name": "",
"statusCode": "",
"headers": {},
"x": 770,
"y": 340,
"wires": []
},
{
"id": "81f6db49f4ad354a",
"type": "http in",
"z": "00b02bbd01c91485",
"name": "clear lockout",
"url": "/docker-update-lockouts/clear",
"method": "post",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 210,
"y": 480,
"wires": [
[
"28d736c3d3a4cf14"
]
]
},
{
"id": "7bae4cc1bd52dfd8",
"type": "http response",
"z": "00b02bbd01c91485",
"name": "",
"statusCode": "",
"headers": {},
"x": 770,
"y": 480,
"wires": []
},
{
"id": "28d736c3d3a4cf14",
"type": "function",
"z": "00b02bbd01c91485",
"name": "Clear lockout",
"func": "let attempts = flow.get(\"dockerUpdateAttempts\") || {};\nconst key = (msg.payload.key || \"\").trim(); // trim whitespace\n\n// optionally log all keys for debugging\nnode.log(\"Available keys: \" + Object.keys(attempts).join(\", \"));\nnode.log(\"Requested key: '\" + key + \"'\");\n\nif (key in attempts) { // use 'in' to avoid false negatives\n delete attempts[key];\n flow.set(\"dockerUpdateAttempts\", attempts);\n\n msg.payload = { success: true, cleared: key };\n node.log(`Cleared update lock for ${key}`);\n} else {\n msg.payload = { success: false, error: `No lock found for '${key}'` };\n}\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 490,
"y": 480,
"wires": [
[
"7bae4cc1bd52dfd8"
]
]
},
{
"id": "b113615f19bd2588",
"type": "http in",
"z": "f16653fb4cb05bbe",
"name": "/health in",
"url": "/health",
"method": "get",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 380,
"y": 220,
"wires": [
[
"4bf7d7901301ce7e"
]
]
},
{
"id": "4bf7d7901301ce7e",
"type": "function",
"z": "f16653fb4cb05bbe",
"name": "health check",
"func": "msg.payload = { status: \"ok\" };\nmsg.headers = { \"Content-Type\": \"application/json\" };\nnode.log(msg.payload.status);\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 670,
"y": 220,
"wires": [
[
"269da1ef164eeb72"
]
]
},
{
"id": "269da1ef164eeb72",
"type": "http response",
"z": "f16653fb4cb05bbe",
"name": "",
"statusCode": "",
"headers": {},
"x": 950,
"y": 220,
"wires": []
},
{
"id": "40ed22bfb88a0d78",
"type": "http in",
"z": "d0f45d2b6f117365",
"name": "/uptime-kuma in",
"url": "/uptime-kuma",
"method": "post",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 320,
"y": 200,
"wires": [
[
"46affc0319a44fa9",
"ab7d36db8e9316f8"
]
]
},
{
"id": "4e0b1edf6f60d701",
"type": "http response",
"z": "d0f45d2b6f117365",
"name": "response",
"statusCode": "",
"headers": {},
"x": 860,
"y": 200,
"wires": []
},
{
"id": "46affc0319a44fa9",
"type": "link out",
"z": "d0f45d2b6f117365",
"name": "gotify domain name alerts",
"mode": "link",
"links": [
"bed17b50dd678228"
],
"x": 615,
"y": 120,
"wires": []
},
{
"id": "ab7d36db8e9316f8",
"type": "function",
"z": "d0f45d2b6f117365",
"name": "WEBHOOK /uptime-kuma IN",
"func": "node.log(\n \"Payload:\\n\" +\n JSON.stringify(msg.payload, null, 2)\n);\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 620,
"y": 200,
"wires": [
[
"4e0b1edf6f60d701"
]
]
},
{
"id": "0301a3cebd1eb23f",
"type": "http request",
"z": "c5240b64a962ea54",
"name": "Send to gotify",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "https://gotify.lan.ddnsgeek.com/message?token=ATSMpSKrdNjKXeT",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [
{
"keyType": "other",
"keyValue": "Content-Type",
"valueType": "other",
"valueValue": "application/json"
}
],
"x": 1300,
"y": 280,
"wires": [
[]
]
},
{
"id": "bed17b50dd678228",
"type": "link in",
"z": "c5240b64a962ea54",
"name": "Doman name alerts",
"links": [
"46affc0319a44fa9"
],
"x": 275,
"y": 100,
"wires": [
[
"6e58de21ded7459e"
]
]
},
{
"id": "6e58de21ded7459e",
"type": "function",
"z": "c5240b64a962ea54",
"name": "Domain name alerts",
"func": "const name = msg.payload.monitor.name;\nconst hb = msg.payload.heartbeat;\n\nconst isUp = hb.status === 1;\n\nconst icon = isUp ? \"✅\" : \"🔴\";\nconst state = isUp ? \"UP\" : \"DOWN\";\n\n// Build a short title for the Gotify notification\nconst title = `${icon} ${name} is ${state}`;\nconst time = hb.localDateTime.split(\" \")[1].split(\":\").slice(0, 2).join(\":\");\n// Build a more detailed message\nlet message = `${icon} Monitor: ${name}\\n`;\nmessage += `Status: ${state}\\n`;\nmessage += `Time: ${time}\\n`;\nmessage += `Details: ${hb.msg}`;\n\nif (hb.ping !== undefined) {\n message += `\\nPing: ${hb.ping} ms`;\n}\n\nif (hb.duration !== undefined) {\n message += `\\nCheck Interval: ${hb.duration} sec`;\n}\n\n// Higher priority for down alerts\nmsg.payload = {\n title: title,\n message: message,\n priority: isUp ? 5 : 8\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 500,
"y": 100,
"wires": [
[
"8630c7dfcdbcce50"
]
]
},
{
"id": "8630c7dfcdbcce50",
"type": "function",
"z": "c5240b64a962ea54",
"name": "GOTIFY alert OUT",
"func": "node.log(JSON.stringify(msg.payload, null, 2));\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1070,
"y": 280,
"wires": [
[
"0301a3cebd1eb23f"
]
]
},
{
"id": "0135d283b9edfb01",
"type": "function",
"z": "c5240b64a962ea54",
"name": "Docker Pull Failed",
"func": "const container = msg.container || \"unknown container\";\nconst code = msg.payload.code;\nconst stderr = flow.get(\"pull_stderr\") || \"Unknown error\";\n\nmsg.payload = {\n title: \"Docker Update Failed\",\n message: `Pull failed for ${container}\\n${stderr}`,\n priority: 8\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 490,
"y": 200,
"wires": [
[
"8630c7dfcdbcce50"
]
]
},
{
"id": "ba2c4e239d3b1e1d",
"type": "link in",
"z": "c5240b64a962ea54",
"name": "Docker pull failed alerts",
"links": [
"27ac6d1d2b208f99"
],
"x": 275,
"y": 200,
"wires": [
[
"0135d283b9edfb01"
]
]
},
{
"id": "4eafade32c867e40",
"type": "function",
"z": "c5240b64a962ea54",
"name": "Docker test failed",
"func": "const container = msg.container || \"unknown container\";\nconst code = msg.payload.code;\nconst stderr = flow.get(\"pull_stderr\") || \"Unknown error\";\n\nmsg.payload = {\n title: \"Testing Failed\",\n message: `Testing failed for ${container}`,\n priority: 8\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 490,
"y": 300,
"wires": [
[
"8630c7dfcdbcce50"
]
]
},
{
"id": "10718f4768834442",
"type": "link in",
"z": "c5240b64a962ea54",
"name": "Docker test failed alerts",
"links": [
"49c52031d95c0638"
],
"x": 275,
"y": 300,
"wires": [
[
"4eafade32c867e40"
]
]
},
{
"id": "7d8200040f9b1e83",
"type": "function",
"z": "c5240b64a962ea54",
"name": "Docker update success",
"func": "const container = msg.container || \"unknown container\";\nconst code = msg.payload.code;\nconst stderr = flow.get(\"pull_stderr\") || \"Unknown error\";\n\nmsg.payload = {\n title: \"Container Updated\",\n message: `Container: ${container}\n Host: ${msg.host}`,\n priority: 8\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 490,
"y": 380,
"wires": [
[
"8630c7dfcdbcce50"
]
]
},
{
"id": "f5771c9cc8e81779",
"type": "link in",
"z": "c5240b64a962ea54",
"name": "Docker update success alerts",
"links": [
"b083fcf71669254e"
],
"x": 275,
"y": 380,
"wires": [
[
"7d8200040f9b1e83"
]
]
},
{
"id": "c3d07241f4a570af",
"type": "function",
"z": "c5240b64a962ea54",
"name": "Docker updates Unknown Project",
"func": "const container = msg.container || \"unknown container\";\nconst code = msg.payload.code;\nconst stderr = flow.get(\"pull_stderr\") || \"Unknown error\";\nconst project = msg.payload.labels.com_docker_compose_project\nmsg.payload = {\n title: \"Container Updates Failed\",\n message: `The ${container} container has failed.\\n\n Unknown project ${project}`,\n priority: 8\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 520,
"y": 460,
"wires": [
[
"8630c7dfcdbcce50"
]
]
},
{
"id": "7c62509d505c281b",
"type": "link in",
"z": "c5240b64a962ea54",
"name": "Docker update unknown project",
"links": [
"852d039f540dd0d3"
],
"x": 275,
"y": 460,
"wires": [
[
"c3d07241f4a570af"
]
]
},
{
"id": "d1346f7151103832",
"type": "function",
"z": "c5240b64a962ea54",
"name": "Docker updates for Raspi",
"func": "const container = msg.container || \"unknown container\";\nconst code = msg.payload.code;\nconst stderr = flow.get(\"pull_stderr\") || \"Unknown error\";\nconst project = msg.payload.labels.com_docker_compose_project\nmsg.payload = {\n title: \"Container Updates Available - Raspi\",\n message: `The ${container} container has updates available.`,\n priority: 8\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 490,
"y": 520,
"wires": [
[
"8630c7dfcdbcce50"
]
]
},
{
"id": "e6c64cd6d405b8ed",
"type": "link in",
"z": "c5240b64a962ea54",
"name": "Docker update for raspi",
"links": [],
"x": 275,
"y": 520,
"wires": [
[
"d1346f7151103832"
]
]
},
{
"id": "b72f58c8b6d6d265",
"type": "link in",
"z": "c5240b64a962ea54",
"name": "Docker update locked",
"links": [
"0e6520dfb81a951b",
"77675958f77d2d5f"
],
"x": 275,
"y": 580,
"wires": [
[
"1a9798d5c081240a"
]
]
},
{
"id": "1a9798d5c081240a",
"type": "function",
"z": "c5240b64a962ea54",
"name": "Docker updates locked",
"func": "const container = msg.container || \"unknown container\";\nconst code = msg.payload.code;\nconst stderr = flow.get(\"pull_stderr\") || \"Unknown error\";\n//const project = msg.payload.labels.com_docker_compose_project\nmsg.payload = {\n title: \"Update Locked out\",\n message: `Container: ${container}\n Host: ${msg.host}\\n\n Manual Intervention required.`,\n priority: 8\n};\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 480,
"y": 580,
"wires": [
[
"8630c7dfcdbcce50"
]
]
}
]