{
"name": "فحص حالة المواقع — Project OS",
"active": false,
"pinData": {},
"settings": { "executionOrder": "v1" },
"versionId": "site-check-3",
"meta": { "templateCredsSetupCompleted": true },
"tags": [],
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "site-check",
"responseMode": "responseNode",
"options": {}
},
"id": "n1-webhook",
"name": "طلب التشغيل (Webhook)",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"webhookId": "site-check",
"position": [0, 0]
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// تجهيز طلب الفحص الوارد من Project OS\nconst input = $input.first().json;\nconst body = input.body ?? input ?? {};\n\n// المواقع الثلاثة التي يراقبها التطبيق كما تحدّدها متطلبات المشروع (تُستخدم عند عدم إرسال قائمة في الطلب)\nconst DEFAULT_SITES = [\n { label: \"Project OS\", url: \"https://projectos.smvapp.cloud\" },\n { label: \"n8n\", url: \"https://n8n.smvapp.cloud\" },\n { label: \"Student Tutor\", url: \"https://student.smvapp.cloud\" }\n];\n\nlet sites = Array.isArray(body.sites) && body.sites.length ? body.sites.slice(0, 20) : DEFAULT_SITES;\nsites = sites\n .map((s) => ({ label: String(s.label || s.name || s.url || \"موقع\"), url: String(s.url || \"\") }))\n .filter((s) => /^https:\\/\\//.test(s.url));\nif (!sites.length) sites = DEFAULT_SITES;\n\nreturn [{\n json: {\n runId: body.runId ?? body.run_id ?? null,\n callbackUrl: body.callbackUrl ?? body.callback_url ?? null,\n triggeredAt: new Date().toISOString(),\n sites\n }\n}];"
},
"id": "n2-prepare",
"name": "تجهيز طلب الفحص",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [220, 0]
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// توزيع المواقع: عنصر واحد لكل موقع\nconst base = $input.first().json;\nreturn base.sites.map((site) => ({\n json: {\n runId: base.runId,\n callbackUrl: base.callbackUrl,\n label: site.label,\n url: site.url\n }\n}));"
},
"id": "n3-split",
"name": "توزيع المواقع",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [440, 0]
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "n4-loop",
"name": "فحص موقعًا بموقع",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [660, 0]
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// تسجيل لحظة بدء فحص هذا الموقع لحساب زمن الاستجابة بدقة\nreturn {\n json: { ...$json, startedAt: Date.now() }\n};"
},
"id": "n5-start",
"name": "توقيت بداية الفحص",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [880, -160]
},
{
"parameters": {
"url": "={{ $json.url }}",
"options": {
"timeout": 10000,
"response": {
"response": {
"neverError": true,
"responseFormat": "text"
}
}
}
},
"id": "n6-http",
"name": "طلب HTTP حقيقي",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"onError": "continueRegularOutput",
"position": [1100, -160]
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// تجهيز نتيجة فحص موقع واحد وفق عقد site.check ثم العودة إلى حلقة الفحص\nconst j = $json;\nconst statusCode = typeof j.statusCode === \"number\" ? j.statusCode : null;\nconst networkError = j.error ? (j.error.message ?? String(j.error)) : null;\n\n// ok: وصلت استجابة HTTP فعلية ورمزها أقل من 400\nconst ok = statusCode !== null && statusCode < 400;\n\nlet error = null;\nif (!ok && statusCode === null) {\n error = networkError ?? \"لم تصل أي استجابة من الموقع\";\n}\n\nreturn {\n json: {\n runId: j.runId,\n callbackUrl: j.callbackUrl,\n label: j.label,\n url: j.url,\n ok,\n statusCode,\n latencyMs: Math.max(0, Date.now() - j.startedAt),\n checkedAt: new Date().toISOString(),\n error\n }\n};"
},
"id": "n7-result",
"name": "تجهيز نتيجة الفحص",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1320, -160]
},
{
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// تجميع نتائج الفحص في عنصر واحد جاهز للإرسال وفق عقد site.check\nconst items = $input.all();\nconst base = items[0].json;\n\nconst results = items.map((item) => {\n const j = item.json;\n return {\n url: j.url,\n label: j.label,\n ok: j.ok === true,\n statusCode: j.statusCode ?? null,\n latencyMs: j.latencyMs ?? null,\n error: j.error ?? null\n };\n});\n\nreturn [{\n json: {\n runId: base.runId ?? null,\n callbackUrl: base.callbackUrl ?? null,\n kind: \"site.check\",\n completedAt: new Date().toISOString(),\n summary: {\n total: results.length,\n ok: results.filter((r) => r.ok).length,\n down: results.filter((r) => !r.ok).length\n },\n results\n }\n}];"
},
"id": "n8-aggregate",
"name": "تجميع النتائج",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [880, 160]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "has-callback",
"leftValue": "={{ $json.callbackUrl }}",
"rightValue": "",
"operator": { "type": "string", "operation": "notEmpty", "singleValue": true }
}
],
"combinator": "and"
},
"options": {}
},
"id": "n9-if",
"name": "هل يوجد رابط Callback؟",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [1100, 160]
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.callbackUrl }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ runId: $json.runId, kind: $json.kind, completedAt: $json.completedAt, summary: $json.summary, results: $json.results }) }}",
"options": {
"timeout": 10000,
"response": { "response": { "neverError": true } }
}
},
"id": "n10-callback",
"name": "إرسال النتائج إلى Project OS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"onError": "continueRegularOutput",
"position": [1320, 40]
},
{
"parameters": {
"respondWith": "firstIncomingItem",
"options": { "responseCode": 200 }
},
"id": "n11-respond",
"name": "إرجاع النتائج للمُشغِّل",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [1540, 160]
}
],
"connections": {
"طلب التشغيل (Webhook)": {
"main": [[{ "node": "تجهيز طلب الفحص", "type": "main", "index": 0 }]]
},
"تجهيز طلب الفحص": {
"main": [[{ "node": "توزيع المواقع", "type": "main", "index": 0 }]]
},
"توزيع المواقع": {
"main": [[{ "node": "فحص موقعًا بموقع", "type": "main", "index": 0 }]]
},
"فحص موقعًا بموقع": {
"main": [
[{ "node": "تجميع النتائج", "type": "main", "index": 0 }],
[{ "node": "توقيت بداية الفحص", "type": "main", "index": 0 }]
]
},
"توقيت بداية الفحص": {
"main": [[{ "node": "طلب HTTP حقيقي", "type": "main", "index": 0 }]]
},
"طلب HTTP حقيقي": {
"main": [[{ "node": "تجهيز نتيجة الفحص", "type": "main", "index": 0 }]]
},
"تجهيز نتيجة الفحص": {
"main": [[{ "node": "فحص موقعًا بموقع", "type": "main", "index": 0 }]]
},
"تجميع النتائج": {
"main": [[{ "node": "هل يوجد رابط Callback؟", "type": "main", "index": 0 }]]
},
"هل يوجد رابط Callback؟": {
"main": [
[{ "node": "إرسال النتائج إلى Project OS", "type": "main", "index": 0 }],
[{ "node": "إرجاع النتائج للمُشغِّل", "type": "main", "index": 0 }]
]
},
"إرسال النتائج إلى Project OS": {
"main": [[{ "node": "إرجاع النتائج للمُشغِّل", "type": "main", "index": 0 }]]
}
}
}