Merge pull request #13 from beatz174-bit/codex/review-grafana-docker-safe-update-flow
Fix Node-RED Grafana safe update flow parsing and lockout notifications
This commit is contained in:
@@ -60,7 +60,7 @@
|
||||
"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;",
|
||||
"func": "const labels = msg.payload.labels || {};\nconst container = labels.container;\nconst image = labels.compose_image || labels.running_image || labels.image;\nconst project = labels.com_docker_compose_project;\n\nconst host = project === \"core\" ? \"docker\" : \"raspi\";\n\nif (!container) {\n node.warn(\"No container found in alert labels\");\n return null;\n}\n\nmsg.payload = `/compose/${host}/services-up.sh --profile all pull -q ${container}`;\nmsg.container = container;\nmsg.image = image;\nmsg.host = host;\n\nnode.log(`New docker update available\n Container: ${container}\n Image: ${image}\n Host: ${host}`);\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
@@ -375,7 +375,7 @@
|
||||
"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];",
|
||||
"func": "let attempts = flow.get(\"dockerUpdateAttempts\") || {};\n\nif (msg.updateKey && attempts[msg.updateKey]) {\n const firstFailure = !attempts[msg.updateKey].notified;\n\n attempts[msg.updateKey].status = \"pull_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` +\n `Image: ${msg.image}\n` +\n `Host: ${msg.host}\n` +\n `Result: ${attempts[msg.updateKey].status}\n\n` +\n `Further Grafana alerts for this update will be ignored until manual intervention.`\n };\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n return [msg, msg];\n }\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n}\n\nnode.log(`Pull image failed\n Command: ${msg.payload}\n Container: ${msg.container}\n Image: ${msg.image}\n Host: ${msg.host}`);\nreturn [msg, null];",
|
||||
"outputs": 2,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
@@ -398,7 +398,7 @@
|
||||
"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];",
|
||||
"func": "let 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` +\n `Image: ${msg.image}\n` +\n `Host: ${msg.host}\n` +\n `Result: ${attempts[msg.updateKey].status}\n\n` +\n `Further Grafana alerts for this update will be ignored until manual intervention.`\n };\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n return [msg, msg];\n }\n\n flow.set(\"dockerUpdateAttempts\", attempts);\n}\n\nnode.log(`Test image failed\n Command: ${msg.payload}\n Container: ${msg.container}\n Image: ${msg.image}\n Host: ${msg.host}`);\nreturn [msg, null];",
|
||||
"outputs": 2,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
@@ -421,7 +421,7 @@
|
||||
"type": "switch",
|
||||
"z": "00b02bbd01c91485",
|
||||
"name": "docker project name",
|
||||
"property": "payload.alerts.labels.com_docker_compose_project",
|
||||
"property": "payload.labels.com_docker_compose_project",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
@@ -460,7 +460,7 @@
|
||||
"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;",
|
||||
"func": "const labels = msg.payload.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} (${image}) failed`);\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
@@ -493,7 +493,7 @@
|
||||
"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;",
|
||||
"func": "const labels = msg.payload.labels || {};\n\nnode.warn(\n `${labels.container || \"unknown\"} ` +\n `(${labels.compose_image || labels.running_image || \"unknown image\"})`\n);\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
@@ -565,7 +565,7 @@
|
||||
"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];",
|
||||
"func": "const labels = msg.payload.labels || {};\n\nconst container = labels.container;\nconst image = labels.compose_image || labels.running_image || labels.image;\nconst project = labels.com_docker_compose_project;\n\nif (!container || !image) {\n node.warn(\"Missing container/image labels; skipping update attempt tracking\");\n return [null, null];\n}\n\n// Load persistent attempt registry\nlet attempts = flow.get(\"dockerUpdateAttempts\") || {};\n\nconst host = project === \"core\" ? \"docker\" : \"raspi\";\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 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\nreturn [msg, null];",
|
||||
"outputs": 2,
|
||||
"timeout": 0,
|
||||
"noerr": 0,
|
||||
@@ -869,7 +869,7 @@
|
||||
"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;",
|
||||
"func": "const name = msg.payload.monitor.name;\nconst hb = msg.payload.heartbeat;\n\nconst isUp = hb.status === 1;\n\nconst icon = isUp ? \"\u2705\" : \"\ud83d\udd34\";\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,
|
||||
@@ -1104,7 +1104,7 @@
|
||||
"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;",
|
||||
"func": "const container = msg.container || \"unknown container\";\nconst defaultPayload = {\n title: \"Update Locked out\",\n message: `Container: ${container}\nHost: ${msg.host}\n\nManual intervention required.`,\n priority: 8\n};\n\nif (msg.notification) {\n msg.payload = {\n title: msg.notification.title || defaultPayload.title,\n message: msg.notification.message || defaultPayload.message,\n priority: 8\n };\n return msg;\n}\n\nmsg.payload = defaultPayload;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": 0,
|
||||
"noerr": 0,
|
||||
|
||||
Reference in New Issue
Block a user