0c77056422
New /health-check endpoint verifies: - Gitea release integrity (manifest version matches tag) - Duplicate ZIP assets in releases - EspoCRM API reachability per customer - Extension load status on each instance - Stale deployments (logged ok but version not updated) New n8n workflow runs at 07:00 & 19:00, combines health check with drift sync, and posts combined report to Mattermost. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
129 lines
6.8 KiB
JSON
129 lines
6.8 KiB
JSON
{
|
|
"name": "Extension Platform: Health & Drift Check (2x Daily)",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{ "triggerAtHour": 7 },
|
|
{ "triggerAtHour": 19 }
|
|
]
|
|
}
|
|
},
|
|
"id": "schedule-trigger",
|
|
"name": "07:00 & 19:00 Daily",
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [0, 0]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://platform.dev.marcus-law.co.il/sync",
|
|
"sendBody": true,
|
|
"bodyParameters": { "parameters": [] },
|
|
"options": { "timeout": 120000 }
|
|
},
|
|
"id": "call-sync",
|
|
"name": "Sync All Customers",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [240, -100]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://platform.dev.marcus-law.co.il/health-check",
|
|
"options": { "timeout": 120000 }
|
|
},
|
|
"id": "call-health",
|
|
"name": "Health Check",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [240, 100]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "wait",
|
|
"options": {}
|
|
},
|
|
"id": "merge-results",
|
|
"name": "Wait for Both",
|
|
"type": "n8n-nodes-base.merge",
|
|
"typeVersion": 3,
|
|
"position": [480, 0]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Combine sync + health check results into a single Mattermost report\nconst items = $input.all();\nconst syncResult = items[0]?.json || {};\nconst healthResult = items[1]?.json || {};\n\nconst lines = [];\nconst now = new Date().toLocaleString('he-IL', { timeZone: 'Asia/Jerusalem' });\n\n// === Health Check Section ===\nconst issues = healthResult.issues || [];\nconst critical = issues.filter(i => i.severity === 'critical');\nconst errors = issues.filter(i => i.severity === 'error');\nconst warnings = issues.filter(i => i.severity === 'warning');\n\nif (issues.length > 0) {\n const statusIcon = critical.length > 0 ? ':rotating_light:' : errors.length > 0 ? ':x:' : ':warning:';\n lines.push(`### ${statusIcon} Extension Health Check — ${now}`);\n lines.push(`**${issues.length}** issues found: ${critical.length} critical, ${errors.length} errors, ${warnings.length} warnings`);\n lines.push('');\n\n if (critical.length > 0) {\n lines.push('#### :rotating_light: Critical');\n for (const i of critical) {\n lines.push(`- **${i.extension || 'System'}** [${i.check}]: ${i.message}`);\n }\n lines.push('');\n }\n if (errors.length > 0) {\n lines.push('#### :x: Errors');\n for (const i of errors) {\n lines.push(`- **${i.extension || 'System'}** [${i.check}]: ${i.message}`);\n }\n lines.push('');\n }\n if (warnings.length > 0) {\n lines.push('#### :warning: Warnings');\n for (const i of warnings) {\n lines.push(`- **${i.extension || 'System'}** [${i.check}]: ${i.message}`);\n }\n lines.push('');\n }\n} else {\n lines.push(`### :white_check_mark: Extension Health Check — ${now}`);\n lines.push('All extensions passed integrity checks.');\n lines.push('');\n}\n\n// Customer API status\nconst customers = healthResult.customers || [];\nif (customers.length > 0) {\n lines.push('| Customer | API | Extensions | Version |');\n lines.push('|----------|-----|------------|---------|');\n for (const c of customers) {\n const apiStatus = c.api_reachable ? ':white_check_mark:' : ':x:';\n const extCount = c.extensions_loaded ? `${c.extension_count} loaded` : ':x: failed';\n lines.push(`| ${c.customer} | ${apiStatus} | ${extCount} | ${c.espo_version || '-'} |`);\n }\n lines.push('');\n}\n\n// === Drift Section ===\nconst totalDrifts = syncResult.total_drifts || 0;\nconst syncResults = syncResult.results || [];\n\nif (totalDrifts > 0) {\n lines.push(`### :mag: Drift Report — ${totalDrifts} drifts`);\n for (const r of syncResults) {\n if (r.status === 'error') {\n lines.push(`#### :x: ${r.customer}: ${r.message}`);\n continue;\n }\n if (!r.drifts || r.drifts.length === 0) continue;\n lines.push(`#### ${r.customer} (${r.drift_count} drifts)`);\n lines.push('| Extension | Type | Platform | Server |');\n lines.push('|-----------|------|----------|--------|');\n for (const d of r.drifts) {\n const type = {\n 'version_mismatch': ':arrows_counterclockwise: Mismatch',\n 'missing_on_server': ':red_circle: Missing',\n 'extra_on_server': ':large_blue_circle: Extra'\n }[d.type] || d.type;\n lines.push(`| ${d.extension} | ${type} | ${d.platform || '-'} | ${d.server || '-'} |`);\n }\n if (r.updated > 0) lines.push(`> Updated ${r.updated} records`);\n lines.push('');\n }\n} else {\n lines.push(`---`);\n lines.push(`:white_check_mark: **Drift check**: ${syncResult.customers_checked || 0} customers, no drifts.`);\n}\n\nreturn [{ json: { message: lines.join('\\n'), has_issues: issues.length > 0 || totalDrifts > 0 } }];"
|
|
},
|
|
"id": "format-report",
|
|
"name": "Format Combined Report",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [700, 0]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://mattermost.dev.marcus-law.co.il/api/v4/posts",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ JSON.stringify({ channel_id: 'bdikot-veichut' in '' ? 'xq9ogsbwwpdbjnny4ggaoin87e' : 'xq9ogsbwwpdbjnny4ggaoin87e', message: $json.message }) }}",
|
|
"options": {}
|
|
},
|
|
"id": "mattermost-post",
|
|
"name": "Post to Mattermost",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [920, 0],
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "mattermost-token",
|
|
"name": "Mattermost Token"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"connections": {
|
|
"07:00 & 19:00 Daily": {
|
|
"main": [
|
|
[
|
|
{ "node": "Sync All Customers", "type": "main", "index": 0 },
|
|
{ "node": "Health Check", "type": "main", "index": 0 }
|
|
]
|
|
]
|
|
},
|
|
"Sync All Customers": {
|
|
"main": [
|
|
[{ "node": "Wait for Both", "type": "main", "index": 0 }]
|
|
]
|
|
},
|
|
"Health Check": {
|
|
"main": [
|
|
[{ "node": "Wait for Both", "type": "main", "index": 1 }]
|
|
]
|
|
},
|
|
"Wait for Both": {
|
|
"main": [
|
|
[{ "node": "Format Combined Report", "type": "main", "index": 0 }]
|
|
]
|
|
},
|
|
"Format Combined Report": {
|
|
"main": [
|
|
[{ "node": "Post to Mattermost", "type": "main", "index": 0 }]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
},
|
|
"tags": [
|
|
{ "name": "extension-platform" },
|
|
{ "name": "health-check" },
|
|
{ "name": "daily" }
|
|
]
|
|
}
|