Spaces:
Build error
Build error
Upload 66 files
Browse files- DeviceManager.py +82 -0
- New Text Document.txt +0 -0
- README_FIXES.md +61 -0
- auto_offload.py +73 -0
- autostart_config.py +1 -0
- background_service.py +1 -0
- briefcase +0 -0
- central_manager.py +2 -1
- config.py +6 -9
- dashboard.py +51 -46
- distributed_executor.py +110 -8
- dts_cli.py +12 -50
- enhanced_assistant.py +1 -0
- external_server.py +98 -0
- global_memory.json +0 -0
- gui.py +22 -0
- internet_scanner.py +5 -4
- launcher.py +1 -0
- live_streaming.py +1 -0
- load_balancer.py +1 -0
- node_client.py +125 -0
- package-lock.json +0 -0
- package.json +6 -10
- peer_discovery.py +69 -111
- peer_registry.py +2 -1
- peer_server.py +15 -3
- quick_connection_test.py +1 -1
- ram_manager.py +162 -0
- remote_executor.py +2 -1
- rpc_server.py +3 -2
- security_layer.py +1 -0
- server.py +3 -2
- smart_tasks.py +2 -1
- system_check.py +2 -1
- system_tray.py +1 -0
- task_splitter.py +1 -0
- test_distributed_system.py +1 -0
- test_monitor.py +1 -0
- video_processing.py +1 -0
- your_tasks.py +1 -0
DeviceManager.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import GPUtil
|
| 3 |
+
import psutil
|
| 4 |
+
import logging
|
| 5 |
+
from peer_discovery import PORT
|
| 6 |
+
|
| 7 |
+
logging.getLogger().setLevel(logging.CRITICAL) # صامت
|
| 8 |
+
|
| 9 |
+
class DeviceManager:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.devices = {
|
| 12 |
+
"GPU": self._detect_gpus(),
|
| 13 |
+
"DSP": self._detect_dsps(),
|
| 14 |
+
"NIC": self._detect_nics(),
|
| 15 |
+
"STORAGE": self._detect_storage(),
|
| 16 |
+
"CAPTURE": self._detect_capture(),
|
| 17 |
+
"ACCELERATOR": self._detect_accelerators()
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
# ────────────── اكتشاف الكروت ──────────────
|
| 21 |
+
def _detect_gpus(self):
|
| 22 |
+
try:
|
| 23 |
+
return GPUtil.getGPUs()
|
| 24 |
+
except:
|
| 25 |
+
return []
|
| 26 |
+
|
| 27 |
+
def _detect_dsps(self):
|
| 28 |
+
try:
|
| 29 |
+
output = subprocess.check_output(["aplay", "-l"], stderr=subprocess.DEVNULL).decode()
|
| 30 |
+
return ["DSP_Audio"] if "card" in output.lower() else []
|
| 31 |
+
except:
|
| 32 |
+
return []
|
| 33 |
+
|
| 34 |
+
def _detect_nics(self):
|
| 35 |
+
try:
|
| 36 |
+
return list(psutil.net_if_addrs().keys())
|
| 37 |
+
except:
|
| 38 |
+
return []
|
| 39 |
+
|
| 40 |
+
def _detect_storage(self):
|
| 41 |
+
try:
|
| 42 |
+
output = subprocess.check_output(["lsblk", "-o", "NAME"], stderr=subprocess.DEVNULL).decode()
|
| 43 |
+
return output.split() if output else []
|
| 44 |
+
except:
|
| 45 |
+
return []
|
| 46 |
+
|
| 47 |
+
def _detect_capture(self):
|
| 48 |
+
try:
|
| 49 |
+
output = subprocess.check_output(["v4l2-ctl", "--list-devices"], stderr=subprocess.DEVNULL).decode()
|
| 50 |
+
return output.split(":")[0::2] if output else []
|
| 51 |
+
except:
|
| 52 |
+
return []
|
| 53 |
+
|
| 54 |
+
def _detect_accelerators(self):
|
| 55 |
+
# افتراض: في المستقبل يمكن إضافة اكتشاف حقيقي لـ FPGA/TPU
|
| 56 |
+
return []
|
| 57 |
+
|
| 58 |
+
# ────────────── فحص الحمل ──────────────
|
| 59 |
+
def get_device_load(self, device_type, index=0):
|
| 60 |
+
try:
|
| 61 |
+
if device_type == "GPU" and self.devices["GPU"]:
|
| 62 |
+
return self.devices["GPU"][index].load * 100
|
| 63 |
+
elif device_type == "DSP" and self.devices["DSP"]:
|
| 64 |
+
return 10 # افتراضي: ما في API للحمل
|
| 65 |
+
elif device_type == "NIC" and self.devices["NIC"]:
|
| 66 |
+
return psutil.net_io_counters().bytes_sent / (1024 * 1024) # مثال بسيط
|
| 67 |
+
elif device_type == "STORAGE" and self.devices["STORAGE"]:
|
| 68 |
+
return psutil.disk_usage('/').percent
|
| 69 |
+
elif device_type == "CAPTURE" and self.devices["CAPTURE"]:
|
| 70 |
+
return 20 # افتراضي: حمل منخفض
|
| 71 |
+
elif device_type == "ACCELERATOR" and self.devices["ACCELERATOR"]:
|
| 72 |
+
return 15 # افتراضي
|
| 73 |
+
return 0
|
| 74 |
+
except:
|
| 75 |
+
return 0
|
| 76 |
+
|
| 77 |
+
# ────────────── منطق 30% / 70% ──────────────
|
| 78 |
+
def can_receive(self, device_type, index=0):
|
| 79 |
+
return self.get_device_load(device_type, index) <= 30
|
| 80 |
+
|
| 81 |
+
def should_offload(self, device_type, index=0):
|
| 82 |
+
return self.get_device_load(device_type, index) >= 70
|
New Text Document.txt
ADDED
|
File without changes
|
README_FIXES.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AmalOffload v6 — Fix Pack
|
| 2 |
+
|
| 3 |
+
تم إنشاء نسخة مُرقّعة من المشروع مع إصلاحات أساسية تمنع الأعطال الفورية وتسهّل التشغيل.
|
| 4 |
+
|
| 5 |
+
## ما الذي تم إصلاحه؟
|
| 6 |
+
- autostart_config.py: dedup import of PORT
|
| 7 |
+
- background_service.py: dedup import of PORT
|
| 8 |
+
- central_manager.py: dedup import of PORT
|
| 9 |
+
- control.py: dedup import of PORT
|
| 10 |
+
- dashboard.py: dedup import of PORT
|
| 11 |
+
- DeviceManager.py: dedup import of PORT
|
| 12 |
+
- distributed_executor.py: dedup import of PORT
|
| 13 |
+
- dts_cli.py: dedup import of PORT
|
| 14 |
+
- enhanced_assistant.py: dedup import of PORT
|
| 15 |
+
- external_server.py: dedup import of PORT
|
| 16 |
+
- internet_scanner.py: dedup import of PORT
|
| 17 |
+
- launcher.py: dedup import of PORT
|
| 18 |
+
- live_streaming.py: dedup import of PORT
|
| 19 |
+
- load_balancer.py: dedup import of PORT
|
| 20 |
+
- peer_registry.py: dedup import of PORT
|
| 21 |
+
- peer_server.py: dedup import of PORT
|
| 22 |
+
- remote_executor.py: dedup import of PORT
|
| 23 |
+
- rpc_server.py: dedup import of PORT
|
| 24 |
+
- security_layer.py: dedup import of PORT
|
| 25 |
+
- server.py: dedup import of PORT
|
| 26 |
+
- smart_tasks.py: dedup import of PORT
|
| 27 |
+
- system_check.py: dedup import of PORT
|
| 28 |
+
- system_tray.py: dedup import of PORT
|
| 29 |
+
- task_splitter.py: dedup import of PORT
|
| 30 |
+
- test_distributed_system.py: dedup import of PORT
|
| 31 |
+
- test_monitor.py: dedup import of PORT
|
| 32 |
+
- video_processing.py: dedup import of PORT
|
| 33 |
+
- your_tasks.py: dedup import of PORT
|
| 34 |
+
- control.py: fixed broken import line
|
| 35 |
+
- dts_cli.py: rewritten to a minimal, valid CLI
|
| 36 |
+
- ram_manager.py: removed BOM
|
| 37 |
+
- templates/dashboard.html: created from index.html
|
| 38 |
+
- config.py: added as a single source of truth for PORT
|
| 39 |
+
|
| 40 |
+
## توصيات إضافية (يدويّة)
|
| 41 |
+
- أضِف `fastapi` و`uvicorn` إلى `requirements.txt` إذا أردت تشغيل `central_manager.py` (يستخدم FastAPI).
|
| 42 |
+
- وحّد استيراد المنفذ عبر:
|
| 43 |
+
```python
|
| 44 |
+
from config import PORT
|
| 45 |
+
```
|
| 46 |
+
واستبدل أي استخدام لـ `from peer_discovery import PORT, PORT` أو `CPU_PORT` حسب الحاجة.
|
| 47 |
+
- إن ظهرت مشكلة `TemplateNotFound: dashboard.html` فقد تم إنشاء `templates/dashboard.html` تلقائياً.
|
| 48 |
+
- يُفضّل تنظيف `requirements.txt` من التكرارات (انظر التوصيات في تقرير التدقيق).
|
| 49 |
+
- اختر نقطة دخول واحدة للمشروع (مثلاً: `launcher.py` أو `startup.py`) وتجنّب تشغيل أكثر من خادم في نفس العملية.
|
| 50 |
+
|
| 51 |
+
## التشغيل السريع المقترح
|
| 52 |
+
1) ثبّت المتطلبات (الأساسية فقط):
|
| 53 |
+
```bash
|
| 54 |
+
pip install flask flask_cors flask_socketio requests psutil zeroconf cryptography numpy networkx GPUtil python-dotenv
|
| 55 |
+
```
|
| 56 |
+
2) شغّل الـ Dashboard وخادم RPC معاً:
|
| 57 |
+
```bash
|
| 58 |
+
python dts_cli.py start
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
بالتوفيق!
|
auto_offload.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import torch
|
| 3 |
+
import GPUtil
|
| 4 |
+
import psutil
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
logging.getLogger().setLevel(logging.CRITICAL) # صامت تمامًا
|
| 8 |
+
|
| 9 |
+
class DeviceManager:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.gpus = self._detect_gpus()
|
| 12 |
+
self.dsps = self._detect_dsps()
|
| 13 |
+
# في المستقبل يمكن إضافة كروت أخرى بنفس النمط
|
| 14 |
+
|
| 15 |
+
def _detect_gpus(self):
|
| 16 |
+
"""اكتشاف جميع الـ GPUs المتاحة"""
|
| 17 |
+
try:
|
| 18 |
+
return GPUtil.getGPUs()
|
| 19 |
+
except Exception:
|
| 20 |
+
return []
|
| 21 |
+
|
| 22 |
+
def _detect_dsps(self):
|
| 23 |
+
"""تحقق من وجود DSP أو كروت صوتية"""
|
| 24 |
+
try:
|
| 25 |
+
output = subprocess.check_output(["aplay", "-l"], stderr=subprocess.DEVNULL).decode()
|
| 26 |
+
if "card" in output.lower():
|
| 27 |
+
return ["DSP_Audio"]
|
| 28 |
+
return []
|
| 29 |
+
except Exception:
|
| 30 |
+
return []
|
| 31 |
+
|
| 32 |
+
def get_device_load(self, device_type, index=0):
|
| 33 |
+
"""إرجاع نسبة الحمل للجهاز"""
|
| 34 |
+
if device_type == "GPU" and self.gpus:
|
| 35 |
+
try:
|
| 36 |
+
return self.gpus[index].load * 100 # نسبة مئوية
|
| 37 |
+
except Exception:
|
| 38 |
+
return 0
|
| 39 |
+
elif device_type == "DSP" and self.dsps:
|
| 40 |
+
# هنا ما في API رسمية، نفترض DSP قليل الحمل دائمًا كمثال
|
| 41 |
+
return 10
|
| 42 |
+
return 0
|
| 43 |
+
|
| 44 |
+
class AutoOffloadExecutor:
|
| 45 |
+
def __init__(self, executor):
|
| 46 |
+
self.executor = executor
|
| 47 |
+
self.devices = DeviceManager()
|
| 48 |
+
|
| 49 |
+
def _should_offload(self, device_type, index=0):
|
| 50 |
+
load = self.devices.get_device_load(device_type, index)
|
| 51 |
+
return load >= 70
|
| 52 |
+
|
| 53 |
+
def _can_receive(self, device_type, index=0):
|
| 54 |
+
load = self.devices.get_device_load(device_type, index)
|
| 55 |
+
return load <= 30
|
| 56 |
+
|
| 57 |
+
def submit_auto(self, task_func, *args, task_type=None, **kwargs):
|
| 58 |
+
"""إرسال تلقائي حسب حالة الكروت"""
|
| 59 |
+
device_type = "CPU" # افتراضي
|
| 60 |
+
if task_type == "video" and self.devices.gpus:
|
| 61 |
+
device_type = "GPU"
|
| 62 |
+
elif task_type == "audio" and self.devices.dsps:
|
| 63 |
+
device_type = "DSP"
|
| 64 |
+
|
| 65 |
+
if self._should_offload(device_type):
|
| 66 |
+
# حمل عالي → أرسل
|
| 67 |
+
self.executor.submit(task_func, *args, **kwargs)
|
| 68 |
+
elif self._can_receive(device_type):
|
| 69 |
+
# حمل منخفض → نفذ محليًا
|
| 70 |
+
task_func(*args, **kwargs)
|
| 71 |
+
else:
|
| 72 |
+
# حمل متوسط → نفذ محليًا
|
| 73 |
+
task_func(*args, **kwargs)
|
autostart_config.py
CHANGED
|
@@ -2,6 +2,7 @@ import json
|
|
| 2 |
import os
|
| 3 |
import platform
|
| 4 |
from pathlib import Path
|
|
|
|
| 5 |
|
| 6 |
class AutoStartManager:
|
| 7 |
def __init__(self, app_name="DistributedTaskSystem"):
|
|
|
|
| 2 |
import os
|
| 3 |
import platform
|
| 4 |
from pathlib import Path
|
| 5 |
+
from peer_discovery import PORT
|
| 6 |
|
| 7 |
class AutoStartManager:
|
| 8 |
def __init__(self, app_name="DistributedTaskSystem"):
|
background_service.py
CHANGED
|
@@ -16,6 +16,7 @@ from pathlib import Path
|
|
| 16 |
from flask import Flask, jsonify, request
|
| 17 |
import json
|
| 18 |
from datetime import datetime
|
|
|
|
| 19 |
|
| 20 |
class BackgroundService:
|
| 21 |
def __init__(self):
|
|
|
|
| 16 |
from flask import Flask, jsonify, request
|
| 17 |
import json
|
| 18 |
from datetime import datetime
|
| 19 |
+
from peer_discovery import PORT
|
| 20 |
|
| 21 |
class BackgroundService:
|
| 22 |
def __init__(self):
|
briefcase
ADDED
|
File without changes
|
central_manager.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import Dict, List
|
|
| 8 |
import requests
|
| 9 |
from fastapi import FastAPI, HTTPException
|
| 10 |
from pydantic import BaseModel
|
|
|
|
| 11 |
|
| 12 |
# ---- إعداد FastAPI ----------------------------------------------------------
|
| 13 |
app = FastAPI(title="Central Task Manager")
|
|
@@ -16,7 +17,7 @@ app = FastAPI(title="Central Task Manager")
|
|
| 16 |
|
| 17 |
class RegisterRequest(BaseModel):
|
| 18 |
"""تسجيل أو تجديد ظهور العقدة."""
|
| 19 |
-
url: str # مثلاً: "http://203.0.113.45:
|
| 20 |
load: float = 0.0 # نسبة تحميل العقدة (0.0 - 1.0)، اختياري
|
| 21 |
|
| 22 |
class TaskRequest(BaseModel):
|
|
|
|
| 8 |
import requests
|
| 9 |
from fastapi import FastAPI, HTTPException
|
| 10 |
from pydantic import BaseModel
|
| 11 |
+
from peer_discovery import PORT
|
| 12 |
|
| 13 |
# ---- إعداد FastAPI ----------------------------------------------------------
|
| 14 |
app = FastAPI(title="Central Task Manager")
|
|
|
|
| 17 |
|
| 18 |
class RegisterRequest(BaseModel):
|
| 19 |
"""تسجيل أو تجديد ظهور العقدة."""
|
| 20 |
+
url: str # مثلاً: "http://203.0.113.45:PORT/run"
|
| 21 |
load: float = 0.0 # نسبة تحميل العقدة (0.0 - 1.0)، اختياري
|
| 22 |
|
| 23 |
class TaskRequest(BaseModel):
|
config.py
CHANGED
|
@@ -1,10 +1,7 @@
|
|
| 1 |
-
# config.py
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
SYSTEM_PROMPT = """
|
| 8 |
-
أنت مساعد ذكاء اصطناعي يتحكم به المستخدم عبر أوامر صوتية وكتابية،
|
| 9 |
-
يجب أن تستجيب بسرعة، وبدقة، وتتعلم من التعليمات الجديدة.
|
| 10 |
-
"""
|
|
|
|
| 1 |
+
# config.py — نقطة مركزية لإعدادات المشروع
|
| 2 |
+
import os, random
|
| 3 |
|
| 4 |
+
# مصدر الحقيقة الوحيد لمنفذ الخدمة
|
| 5 |
+
PORT = int(os.getenv("CPU_PORT", os.getenv("PORT", "0")))
|
| 6 |
+
if PORT == 0:
|
| 7 |
+
PORT = random.randint(5000, 9999)
|
|
|
|
|
|
|
|
|
|
|
|
dashboard.py
CHANGED
|
@@ -1,55 +1,60 @@
|
|
| 1 |
-
# dashboard.py
|
| 2 |
-
from flask import Flask, render_template, jsonify
|
| 3 |
-
from peer_discovery import discover_peers
|
| 4 |
-
import threading
|
| 5 |
-
import time
|
| 6 |
-
from typing import List, Dict
|
| 7 |
import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
app = Flask(__name__)
|
| 10 |
-
app.logger.setLevel(logging.INFO)
|
| 11 |
|
| 12 |
-
|
| 13 |
-
current_peers: Dict[str, List[Dict[str, str]]] = {"local": [], "external": []}
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
while True:
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
total_peers = len(new_peers["local"]) + len(new_peers["external"])
|
| 23 |
-
app.logger.info(f"تم تحديث قائمة الأقران: {total_peers} جهاز")
|
| 24 |
-
except Exception as e:
|
| 25 |
-
app.logger.error(f"خطأ في اكتشاف الأقران: {str(e)}")
|
| 26 |
-
time.sleep(10)
|
| 27 |
|
|
|
|
| 28 |
@app.route("/")
|
| 29 |
-
def
|
| 30 |
-
|
| 31 |
-
total_peers = len(current_peers["local"]) + len(current_peers["external"])
|
| 32 |
-
return render_template("dashboard.html",
|
| 33 |
-
peers_count=total_peers,
|
| 34 |
-
last_update=time.strftime("%Y-%m-%d %H:%M:%S"))
|
| 35 |
-
|
| 36 |
-
@app.route("/api/peers")
|
| 37 |
-
def get_peers() -> dict:
|
| 38 |
-
"""واجهة API للحصول على قائمة الأقران"""
|
| 39 |
-
total_peers = len(current_peers["local"]) + len(current_peers["external"])
|
| 40 |
-
return jsonify({
|
| 41 |
-
"peers": current_peers,
|
| 42 |
-
"count": total_peers,
|
| 43 |
-
"status": "success"
|
| 44 |
-
})
|
| 45 |
-
|
| 46 |
-
def start_background_thread() -> None:
|
| 47 |
-
"""بدء خيط الخلفية لتحديث الأقران"""
|
| 48 |
-
thread = threading.Thread(target=update_peers_loop)
|
| 49 |
-
thread.daemon = True
|
| 50 |
-
thread.start()
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import logging
|
| 2 |
+
import socket
|
| 3 |
+
import threading
|
| 4 |
+
import psutil
|
| 5 |
+
import GPUtil
|
| 6 |
+
from flask import Flask, render_template
|
| 7 |
+
from flask_socketio import SocketIO, emit
|
| 8 |
+
from peer_discovery import PEERS
|
| 9 |
+
from peer_discovery import PORT
|
| 10 |
|
|
|
|
|
|
|
| 11 |
|
| 12 |
+
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 13 |
|
| 14 |
+
app = Flask(__name__)
|
| 15 |
+
socketio = SocketIO(app, cors_allowed_origins="*")
|
| 16 |
+
|
| 17 |
+
node_id = socket.gethostname()
|
| 18 |
+
connected_peers_data = {}
|
| 19 |
+
|
| 20 |
+
# ─────────────── جمع بيانات الحمل ───────────────
|
| 21 |
+
def get_system_status():
|
| 22 |
+
cpu = psutil.cpu_percent()
|
| 23 |
+
ram = psutil.virtual_memory().percent
|
| 24 |
+
gpu_load = 0
|
| 25 |
+
try:
|
| 26 |
+
gpus = GPUtil.getGPUs()
|
| 27 |
+
if gpus:
|
| 28 |
+
gpu_load = gpus[0].load * 100
|
| 29 |
+
except:
|
| 30 |
+
pass
|
| 31 |
+
return {"cpu": cpu, "ram": ram, "gpu": gpu_load}
|
| 32 |
+
|
| 33 |
+
# ─────────────── إرسال التحديثات ───────────────
|
| 34 |
+
def broadcast_status():
|
| 35 |
while True:
|
| 36 |
+
status = get_system_status()
|
| 37 |
+
socketio.emit("status_update", {node_id: status}, broadcast=True)
|
| 38 |
+
socketio.sleep(5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
# ─────────────── صفحة الواجهة ───────────────
|
| 41 |
@app.route("/")
|
| 42 |
+
def index():
|
| 43 |
+
return render_template("dashboard.html", peers=connected_peers_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
# ─────────────── استقبال تحديثات الحالة ───────────────
|
| 46 |
+
@socketio.on("status_update")
|
| 47 |
+
def handle_status_update(data):
|
| 48 |
+
connected_peers_data.update(data)
|
| 49 |
+
emit("update_peers", connected_peers_data, broadcast=True)
|
| 50 |
+
|
| 51 |
+
# ─────────────── دردشة ───────────────
|
| 52 |
+
@socketio.on("send_message")
|
| 53 |
+
def handle_message(data):
|
| 54 |
+
emit("receive_message", data, broadcast=True)
|
| 55 |
|
| 56 |
+
# ─────────────── تشغيل ───────────────
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
threading.Thread(target=broadcast_status).start()
|
| 59 |
+
logging.info(f"🚀 تشغيل Dashboard على {node_id}")
|
| 60 |
+
socketio.run(app, host="0.0.0.0", port=7000)
|
distributed_executor.py
CHANGED
|
@@ -1,15 +1,97 @@
|
|
| 1 |
import threading
|
| 2 |
import queue
|
| 3 |
import time
|
| 4 |
-
import json
|
| 5 |
from typing import Callable, Dict, List
|
| 6 |
import socket
|
| 7 |
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo
|
| 8 |
import logging
|
| 9 |
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
logging.basicConfig(level=logging.INFO)
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
class PeerRegistry:
|
| 14 |
def __init__(self):
|
| 15 |
self._peers = {}
|
|
@@ -60,7 +142,7 @@ class PeerRegistry:
|
|
| 60 |
self.add_service(zc, type_, name)
|
| 61 |
|
| 62 |
def remove_service(self, zc, type_, name):
|
| 63 |
-
pass
|
| 64 |
|
| 65 |
listener = Listener()
|
| 66 |
ServiceBrowser(self._zeroconf, "_tasknode._tcp.local.", listener)
|
|
@@ -78,7 +160,7 @@ class PeerRegistry:
|
|
| 78 |
s.close()
|
| 79 |
return ip
|
| 80 |
|
| 81 |
-
|
| 82 |
class DistributedExecutor:
|
| 83 |
def __init__(self, shared_secret: str):
|
| 84 |
self.peer_registry = PeerRegistry()
|
|
@@ -86,6 +168,7 @@ class DistributedExecutor:
|
|
| 86 |
self.task_queue = queue.PriorityQueue()
|
| 87 |
self.result_cache = {}
|
| 88 |
self.available_peers = []
|
|
|
|
| 89 |
self._init_peer_discovery()
|
| 90 |
|
| 91 |
def _init_peer_discovery(self):
|
|
@@ -97,7 +180,26 @@ class DistributedExecutor:
|
|
| 97 |
|
| 98 |
threading.Thread(target=discovery_loop, daemon=True).start()
|
| 99 |
|
| 100 |
-
def submit(self, task_func: Callable, *args, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
task_id = f"{task_func.__name__}_{time.time()}"
|
| 102 |
|
| 103 |
task = {
|
|
@@ -122,6 +224,7 @@ class DistributedExecutor:
|
|
| 122 |
self._send_to_peer(peer, task)
|
| 123 |
else:
|
| 124 |
logging.warning("⚠️ لا توجد أجهزة متاحة - سيتم تنفيذ المهمة محلياً")
|
|
|
|
| 125 |
|
| 126 |
def _is_local_ip(self, ip: str) -> bool:
|
| 127 |
return (
|
|
@@ -142,14 +245,13 @@ class DistributedExecutor:
|
|
| 142 |
logging.error(f"❌ فشل إرسال المهمة لـ {peer['node_id']}: {e}")
|
| 143 |
return None
|
| 144 |
|
| 145 |
-
|
| 146 |
if __name__ == "__main__":
|
| 147 |
executor = DistributedExecutor("my_secret_key")
|
| 148 |
-
executor.peer_registry.register_service("node1",
|
| 149 |
print("✅ نظام توزيع المهام جاهز...")
|
| 150 |
|
| 151 |
def example_task(x):
|
| 152 |
return x * x
|
| 153 |
|
| 154 |
-
executor.submit(example_task, 5)
|
| 155 |
-
|
|
|
|
| 1 |
import threading
|
| 2 |
import queue
|
| 3 |
import time
|
|
|
|
| 4 |
from typing import Callable, Dict, List
|
| 5 |
import socket
|
| 6 |
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo
|
| 7 |
import logging
|
| 8 |
import requests
|
| 9 |
+
import subprocess
|
| 10 |
+
import psutil
|
| 11 |
+
import GPUtil
|
| 12 |
+
from processor_manager import ResourceMonitor
|
| 13 |
+
from peer_discovery import PORT
|
| 14 |
+
|
| 15 |
|
| 16 |
logging.basicConfig(level=logging.INFO)
|
| 17 |
|
| 18 |
+
# ─────────────── Device Manager عام ───────────────
|
| 19 |
+
class DeviceManager:
|
| 20 |
+
def __init__(self):
|
| 21 |
+
self.devices = {
|
| 22 |
+
"GPU": self._detect_gpus(),
|
| 23 |
+
"DSP": self._detect_dsps(),
|
| 24 |
+
"NIC": self._detect_nics(),
|
| 25 |
+
"STORAGE": self._detect_storage(),
|
| 26 |
+
"CAPTURE": self._detect_capture(),
|
| 27 |
+
"ACCELERATOR": self._detect_accelerators()
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
# اكتشاف الأجهزة
|
| 31 |
+
def _detect_gpus(self):
|
| 32 |
+
try:
|
| 33 |
+
return GPUtil.getGPUs()
|
| 34 |
+
except:
|
| 35 |
+
return []
|
| 36 |
+
|
| 37 |
+
def _detect_dsps(self):
|
| 38 |
+
try:
|
| 39 |
+
output = subprocess.check_output(["aplay", "-l"], stderr=subprocess.DEVNULL).decode()
|
| 40 |
+
return ["DSP_Audio"] if "card" in output.lower() else []
|
| 41 |
+
except:
|
| 42 |
+
return []
|
| 43 |
+
|
| 44 |
+
def _detect_nics(self):
|
| 45 |
+
try:
|
| 46 |
+
return list(psutil.net_if_addrs().keys())
|
| 47 |
+
except:
|
| 48 |
+
return []
|
| 49 |
+
|
| 50 |
+
def _detect_storage(self):
|
| 51 |
+
try:
|
| 52 |
+
output = subprocess.check_output(["lsblk", "-o", "NAME"], stderr=subprocess.DEVNULL).decode()
|
| 53 |
+
return output.split() if output else []
|
| 54 |
+
except:
|
| 55 |
+
return []
|
| 56 |
+
|
| 57 |
+
def _detect_capture(self):
|
| 58 |
+
try:
|
| 59 |
+
output = subprocess.check_output(["v4l2-ctl", "--list-devices"], stderr=subprocess.DEVNULL).decode()
|
| 60 |
+
return output.split(":")[0::2] if output else []
|
| 61 |
+
except:
|
| 62 |
+
return []
|
| 63 |
+
|
| 64 |
+
def _detect_accelerators(self):
|
| 65 |
+
# افتراضي: يمكن إضافة اكتشاف حقيقي مستقبلاً
|
| 66 |
+
return ["Accelerator_Device"]
|
| 67 |
+
|
| 68 |
+
# قياس الحمل
|
| 69 |
+
def get_device_load(self, device_type, index=0):
|
| 70 |
+
try:
|
| 71 |
+
if device_type == "GPU" and self.devices["GPU"]:
|
| 72 |
+
return self.devices["GPU"][index].load * 100
|
| 73 |
+
elif device_type == "DSP" and self.devices["DSP"]:
|
| 74 |
+
return 10 # افتراضي
|
| 75 |
+
elif device_type == "NIC" and self.devices["NIC"]:
|
| 76 |
+
return psutil.net_io_counters().bytes_sent / (1024 * 1024) # MB sent كمثال
|
| 77 |
+
elif device_type == "STORAGE" and self.devices["STORAGE"]:
|
| 78 |
+
return psutil.disk_usage('/').percent
|
| 79 |
+
elif device_type == "CAPTURE" and self.devices["CAPTURE"]:
|
| 80 |
+
return 20 # افتراضي
|
| 81 |
+
elif device_type == "ACCELERATOR" and self.devices["ACCELERATOR"]:
|
| 82 |
+
return 15 # افتراضي
|
| 83 |
+
return 0
|
| 84 |
+
except:
|
| 85 |
+
return 0
|
| 86 |
+
|
| 87 |
+
# منطق الاستقبال/الإرسال
|
| 88 |
+
def can_receive(self, device_type, index=0):
|
| 89 |
+
return self.get_device_load(device_type, index) <= 30
|
| 90 |
+
|
| 91 |
+
def should_offload(self, device_type, index=0):
|
| 92 |
+
return self.get_device_load(device_type, index) >= 70
|
| 93 |
+
|
| 94 |
+
# ─────────────── Peer Registry ───────────────
|
| 95 |
class PeerRegistry:
|
| 96 |
def __init__(self):
|
| 97 |
self._peers = {}
|
|
|
|
| 142 |
self.add_service(zc, type_, name)
|
| 143 |
|
| 144 |
def remove_service(self, zc, type_, name):
|
| 145 |
+
pass
|
| 146 |
|
| 147 |
listener = Listener()
|
| 148 |
ServiceBrowser(self._zeroconf, "_tasknode._tcp.local.", listener)
|
|
|
|
| 160 |
s.close()
|
| 161 |
return ip
|
| 162 |
|
| 163 |
+
# ─────────────── Distributed Executor ───────────────
|
| 164 |
class DistributedExecutor:
|
| 165 |
def __init__(self, shared_secret: str):
|
| 166 |
self.peer_registry = PeerRegistry()
|
|
|
|
| 168 |
self.task_queue = queue.PriorityQueue()
|
| 169 |
self.result_cache = {}
|
| 170 |
self.available_peers = []
|
| 171 |
+
self.devices = DeviceManager()
|
| 172 |
self._init_peer_discovery()
|
| 173 |
|
| 174 |
def _init_peer_discovery(self):
|
|
|
|
| 180 |
|
| 181 |
threading.Thread(target=discovery_loop, daemon=True).start()
|
| 182 |
|
| 183 |
+
def submit(self, task_func: Callable, *args, task_type=None, **kwargs):
|
| 184 |
+
monitor = ResourceMonitor().current_load()
|
| 185 |
+
avg_cpu = monitor["average"]["cpu"]
|
| 186 |
+
avg_mem = monitor["average"]["mem_percent"] if "mem_percent" in monitor["average"] else 0
|
| 187 |
+
|
| 188 |
+
# تحديد نوع الجهاز
|
| 189 |
+
device_type = task_type.upper() if task_type else "CPU"
|
| 190 |
+
|
| 191 |
+
# فحص الحمل
|
| 192 |
+
if (avg_cpu > 0.6 or avg_mem > 85 or self.devices.should_offload(device_type)):
|
| 193 |
+
logging.info(f"⚠️ الحمل مرتفع على {device_type} - إرسال المهمة للأقران")
|
| 194 |
+
self._offload_task(task_func, *args, **kwargs)
|
| 195 |
+
elif (avg_cpu <= 0.3 and self.devices.can_receive(device_type)):
|
| 196 |
+
logging.info(f"✅ الحمل منخفض على {device_type} - تنفيذ المهمة محلياً")
|
| 197 |
+
return task_func(*args, **kwargs)
|
| 198 |
+
else:
|
| 199 |
+
logging.info(f"ℹ️ الحمل متوسط على {device_type} - تنفيذ المهمة محلياً")
|
| 200 |
+
return task_func(*args, **kwargs)
|
| 201 |
+
|
| 202 |
+
def _offload_task(self, task_func: Callable, *args, **kwargs):
|
| 203 |
task_id = f"{task_func.__name__}_{time.time()}"
|
| 204 |
|
| 205 |
task = {
|
|
|
|
| 224 |
self._send_to_peer(peer, task)
|
| 225 |
else:
|
| 226 |
logging.warning("⚠️ لا توجد أجهزة متاحة - سيتم تنفيذ المهمة محلياً")
|
| 227 |
+
task_func(*args, **kwargs)
|
| 228 |
|
| 229 |
def _is_local_ip(self, ip: str) -> bool:
|
| 230 |
return (
|
|
|
|
| 245 |
logging.error(f"❌ فشل إرسال المهمة لـ {peer['node_id']}: {e}")
|
| 246 |
return None
|
| 247 |
|
| 248 |
+
# ─────────────── تشغيل رئيسي ───────────────
|
| 249 |
if __name__ == "__main__":
|
| 250 |
executor = DistributedExecutor("my_secret_key")
|
| 251 |
+
executor.peer_registry.register_service("node1", PORT, load=0.1)
|
| 252 |
print("✅ نظام توزيع المهام جاهز...")
|
| 253 |
|
| 254 |
def example_task(x):
|
| 255 |
return x * x
|
| 256 |
|
| 257 |
+
executor.submit(example_task, 5, task_type="GPU")
|
|
|
dts_cli.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
-
# dts_cli.py
|
| 2 |
import click
|
| 3 |
-
from dashboard import app
|
| 4 |
-
from rpc_server import app as rpc_app
|
| 5 |
import threading
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
@click.group()
|
| 8 |
def cli():
|
|
@@ -10,56 +11,17 @@ def cli():
|
|
| 10 |
|
| 11 |
@cli.command()
|
| 12 |
def start():
|
| 13 |
-
"""بدء ا
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
)
|
| 20 |
-
dashboard_thread.daemon = True
|
| 21 |
-
dashboard_thread.start()
|
| 22 |
-
|
| 23 |
-
# تشغيل خادم RPC
|
| 24 |
-
rpc_app.run(host="0.0.0.0", port=7520)
|
| 25 |
|
| 26 |
@cli.command()
|
| 27 |
-
from flask import Flask, render_template, request
|
| 28 |
-
|
| 29 |
-
# ... (الكود الحالي)
|
| 30 |
-
|
| 31 |
-
@flask_app.route("/")
|
| 32 |
-
def home():
|
| 33 |
-
tasks = {
|
| 34 |
-
"1": ("ضرب المصفوفات", "matrix"),
|
| 35 |
-
"2": ("حساب الأعداد الأولية", "prime"),
|
| 36 |
-
"3": ("معالجة البيانات", "data"),
|
| 37 |
-
}
|
| 38 |
-
return render_template("index.html", tasks=tasks)
|
| 39 |
-
|
| 40 |
-
@flask_app.route("/run_task", methods=["POST"])
|
| 41 |
-
def run_task():
|
| 42 |
-
task_id = request.form.get("task_id")
|
| 43 |
-
result = None
|
| 44 |
-
|
| 45 |
-
if task_id == "1":
|
| 46 |
-
result = matrix_multiply(500) # استبدل بمعاملاتك الفعلية
|
| 47 |
-
elif task_id == "2":
|
| 48 |
-
result = prime_calculation(100_000)
|
| 49 |
-
elif task_id == "3":
|
| 50 |
-
result = data_processing(10_000)
|
| 51 |
-
|
| 52 |
-
return render_template("index.html", tasks={
|
| 53 |
-
"1": ("ضرب المصفوفات", "matrix"),
|
| 54 |
-
"2": ("حساب الأعداد الأولية", "prime"),
|
| 55 |
-
"3": ("معالجة البيانات", "data"),
|
| 56 |
-
}, result=result)
|
| 57 |
def discover():
|
| 58 |
-
"""ع
|
| 59 |
-
|
| 60 |
-
peers = discover_peers()
|
| 61 |
-
print("الأجهزة المتصلة:")
|
| 62 |
-
for i, peer in enumerate(peers, 1):
|
| 63 |
print(f"{i}. {peer}")
|
| 64 |
|
| 65 |
if __name__ == "__main__":
|
|
|
|
| 1 |
+
# dts_cli.py — أوامر CLI لتشغيل النظام الموزع
|
| 2 |
import click
|
|
|
|
|
|
|
| 3 |
import threading
|
| 4 |
+
from peer_discovery import PEERS, PORT
|
| 5 |
+
import dashboard
|
| 6 |
+
import rpc_server
|
| 7 |
|
| 8 |
@click.group()
|
| 9 |
def cli():
|
|
|
|
| 11 |
|
| 12 |
@cli.command()
|
| 13 |
def start():
|
| 14 |
+
"""بدء Dashboard وخادم RPC معاً"""
|
| 15 |
+
# شغّل الـ Dashboard في خيط منفصل
|
| 16 |
+
t = threading.Thread(target=lambda: dashboard.socketio.run(dashboard.app, host="0.0.0.0", port=7000), daemon=True)
|
| 17 |
+
t.start()
|
| 18 |
+
# شغّل خادم RPC في الخيط الرئيسي
|
| 19 |
+
rpc_server.app.run(host="0.0.0.0", port=PORT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
@cli.command()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
def discover():
|
| 23 |
+
"""طباعة قائمة الأقران المكتشفة"""
|
| 24 |
+
for i, peer in enumerate(sorted(PEERS), 1):
|
|
|
|
|
|
|
|
|
|
| 25 |
print(f"{i}. {peer}")
|
| 26 |
|
| 27 |
if __name__ == "__main__":
|
enhanced_assistant.py
CHANGED
|
@@ -9,6 +9,7 @@ from PIL import Image
|
|
| 9 |
from io import BytesIO
|
| 10 |
import re
|
| 11 |
from datetime import datetime
|
|
|
|
| 12 |
|
| 13 |
class EnhancedNoraAssistant:
|
| 14 |
def __init__(self):
|
|
|
|
| 9 |
from io import BytesIO
|
| 10 |
import re
|
| 11 |
from datetime import datetime
|
| 12 |
+
from peer_discovery import PORT
|
| 13 |
|
| 14 |
class EnhancedNoraAssistant:
|
| 15 |
def __init__(self):
|
external_server.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
external_server.py — سيرفر مركزي لتوزيع المهام + Dashboard تفاعلي
|
| 4 |
+
"""
|
| 5 |
+
import logging
|
| 6 |
+
import requests
|
| 7 |
+
from flask import Flask, request, jsonify, render_template
|
| 8 |
+
from flask_cors import CORS
|
| 9 |
+
from flask_socketio import SocketIO, emit
|
| 10 |
+
from peer_discovery import PEERS
|
| 11 |
+
from peer_discovery import PORT
|
| 12 |
+
|
| 13 |
+
logging.basicConfig(level=logging.INFO)
|
| 14 |
+
|
| 15 |
+
app = Flask(__name__)
|
| 16 |
+
CORS(app, resources={r"/*": {"origins": "*"}})
|
| 17 |
+
socketio = SocketIO(app, cors_allowed_origins="*")
|
| 18 |
+
|
| 19 |
+
connected_peers = {} # {node_id: {"cpu":%, "ram":%, "gpu":%}}
|
| 20 |
+
|
| 21 |
+
# ─────────────── اختيار أفضل Peer ───────────────
|
| 22 |
+
def select_best_peer():
|
| 23 |
+
peers_list = list(PEERS)
|
| 24 |
+
if not peers_list:
|
| 25 |
+
logging.warning("⚠️ لا توجد أجهزة مسجلة حالياً.")
|
| 26 |
+
return None
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
peer_loads = []
|
| 30 |
+
for peer_url in peers_list:
|
| 31 |
+
try:
|
| 32 |
+
resp = requests.get(f"{peer_url.replace('/run_task','')}/status", timeout=2)
|
| 33 |
+
if resp.ok:
|
| 34 |
+
data = resp.json()
|
| 35 |
+
peer_loads.append((peer_url, data.get("cpu_load", 100)))
|
| 36 |
+
except:
|
| 37 |
+
continue
|
| 38 |
+
|
| 39 |
+
if not peer_loads:
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
peer_loads.sort(key=lambda x: x[1])
|
| 43 |
+
return peer_loads[0][0]
|
| 44 |
+
except Exception as e:
|
| 45 |
+
logging.error(f"❌ خطأ في اختيار الـ Peer: {e}")
|
| 46 |
+
return None
|
| 47 |
+
|
| 48 |
+
# ─────────────── API توزيع المهام ───────────────
|
| 49 |
+
@app.route("/submit_task", methods=["POST"])
|
| 50 |
+
def submit_task():
|
| 51 |
+
data = request.get_json()
|
| 52 |
+
if not data or "task_id" not in data:
|
| 53 |
+
return jsonify({"error": "يجب تحديد task_id"}), 400
|
| 54 |
+
|
| 55 |
+
peer = select_best_peer()
|
| 56 |
+
if not peer:
|
| 57 |
+
return jsonify({"error": "لا توجد أجهزة متاحة حالياً"}), 503
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
resp = requests.post(peer, json=data, timeout=10)
|
| 61 |
+
if resp.ok:
|
| 62 |
+
return jsonify({"status": "success", "result": resp.json()})
|
| 63 |
+
else:
|
| 64 |
+
return jsonify({"error": "فشل إرسال المهمة"}), 500
|
| 65 |
+
except Exception as e:
|
| 66 |
+
logging.error(f"❌ خطأ في إرسال المهمة: {e}")
|
| 67 |
+
return jsonify({"error": str(e)}), 500
|
| 68 |
+
|
| 69 |
+
# ─────────────── API تحديث حالة الأجهزة ───────────────
|
| 70 |
+
@app.route("/update_status", methods=["POST"])
|
| 71 |
+
def update_status():
|
| 72 |
+
data = request.json
|
| 73 |
+
node_id = data.get("node_id")
|
| 74 |
+
if not node_id:
|
| 75 |
+
return jsonify({"error": "node_id مطلوب"}), 400
|
| 76 |
+
|
| 77 |
+
connected_peers[node_id] = {
|
| 78 |
+
"cpu": data.get("cpu"),
|
| 79 |
+
"ram": data.get("ram"),
|
| 80 |
+
"gpu": data.get("gpu")
|
| 81 |
+
}
|
| 82 |
+
socketio.emit("update_peers", connected_peers, broadcast=True)
|
| 83 |
+
return jsonify({"status": "ok"})
|
| 84 |
+
|
| 85 |
+
# ─────────────── صفحة Dashboard ───────────────
|
| 86 |
+
@app.route("/")
|
| 87 |
+
def index():
|
| 88 |
+
return render_template("dashboard.html")
|
| 89 |
+
|
| 90 |
+
# ─────────────── دردشة ───────────────
|
| 91 |
+
@socketio.on("send_message")
|
| 92 |
+
def handle_message(data):
|
| 93 |
+
socketio.emit("receive_message", data, broadcast=True)
|
| 94 |
+
|
| 95 |
+
# ─────────────── تشغيل السيرفر ───────────────
|
| 96 |
+
if __name__ == "__main__":
|
| 97 |
+
logging.info("🚀 بدء السيرفر المركزي مع Dashboard ودردشة")
|
| 98 |
+
socketio.run(app, host="0.0.0.0", port =5005)
|
global_memory.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
gui.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import toga
|
| 2 |
+
from toga.style import Pack
|
| 3 |
+
from toga.style.pack import COLUMN, CENTER
|
| 4 |
+
|
| 5 |
+
class CoreFlowGUI(toga.App):
|
| 6 |
+
def startup(self):
|
| 7 |
+
label = toga.Label("مرحبًا بك في CoreFlow!", style=Pack(text_align=CENTER, font_size=18))
|
| 8 |
+
button = toga.Button("اضغطني", on_press=self.say_hello, style=Pack(padding=10))
|
| 9 |
+
|
| 10 |
+
box = toga.Box(style=Pack(direction=COLUMN, alignment=CENTER, padding=20))
|
| 11 |
+
box.add(label)
|
| 12 |
+
box.add(button)
|
| 13 |
+
|
| 14 |
+
self.main_window = toga.MainWindow(title=self.formal_name)
|
| 15 |
+
self.main_window.content = box
|
| 16 |
+
self.main_window.show()
|
| 17 |
+
|
| 18 |
+
def say_hello(self, widget):
|
| 19 |
+
self.main_window.info_dialog("تحية", "أهلاً بك يا أسامة 👋")
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
return CoreFlowGUI()
|
internet_scanner.py
CHANGED
|
@@ -8,6 +8,7 @@ import time
|
|
| 8 |
import socket
|
| 9 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 10 |
import logging
|
|
|
|
| 11 |
|
| 12 |
class InternetScanner:
|
| 13 |
def __init__(self):
|
|
@@ -19,7 +20,7 @@ class InternetScanner:
|
|
| 19 |
"208.67.222.0/24", # OpenDNS range
|
| 20 |
]
|
| 21 |
|
| 22 |
-
def scan_ip_range(self, ip_range: str, port: int =
|
| 23 |
"""مسح نطاق IP للبحث عن خوادم DTS"""
|
| 24 |
import ipaddress
|
| 25 |
|
|
@@ -48,7 +49,7 @@ class InternetScanner:
|
|
| 48 |
logging.error(f"خطأ في مسح النطاق {ip_range}: {e}")
|
| 49 |
return []
|
| 50 |
|
| 51 |
-
def check_dts_node(self, ip: str, port: int =
|
| 52 |
"""فحص IP معين للتأكد من وجود خادم DTS مع المشروع"""
|
| 53 |
try:
|
| 54 |
# فحص صفحة الصحة العامة
|
|
@@ -94,7 +95,7 @@ class InternetScanner:
|
|
| 94 |
# البحث في GitHub عن مشاريع DTS
|
| 95 |
github_api = "https://api.github.com/search/repositories"
|
| 96 |
params = {
|
| 97 |
-
"q": "distributed task system port:
|
| 98 |
"sort": "updated",
|
| 99 |
"per_page": 10
|
| 100 |
}
|
|
@@ -122,7 +123,7 @@ class InternetScanner:
|
|
| 122 |
try:
|
| 123 |
# التحقق من صحة IP
|
| 124 |
socket.inet_aton(ip)
|
| 125 |
-
peer_url = f"http://{ip}:
|
| 126 |
|
| 127 |
# فحص سريع
|
| 128 |
if self.check_dts_node(ip):
|
|
|
|
| 8 |
import socket
|
| 9 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 10 |
import logging
|
| 11 |
+
from peer_discovery import PORT
|
| 12 |
|
| 13 |
class InternetScanner:
|
| 14 |
def __init__(self):
|
|
|
|
| 20 |
"208.67.222.0/24", # OpenDNS range
|
| 21 |
]
|
| 22 |
|
| 23 |
+
def scan_ip_range(self, ip_range: str, port: int = PORT):
|
| 24 |
"""مسح نطاق IP للبحث عن خوادم DTS"""
|
| 25 |
import ipaddress
|
| 26 |
|
|
|
|
| 49 |
logging.error(f"خطأ في مسح النطاق {ip_range}: {e}")
|
| 50 |
return []
|
| 51 |
|
| 52 |
+
def check_dts_node(self, ip: str, port: int = PORT) -> str:
|
| 53 |
"""فحص IP معين للتأكد من وجود خادم DTS مع المشروع"""
|
| 54 |
try:
|
| 55 |
# فحص صفحة الصحة العامة
|
|
|
|
| 95 |
# البحث في GitHub عن مشاريع DTS
|
| 96 |
github_api = "https://api.github.com/search/repositories"
|
| 97 |
params = {
|
| 98 |
+
"q": "distributed task system port:PORT",
|
| 99 |
"sort": "updated",
|
| 100 |
"per_page": 10
|
| 101 |
}
|
|
|
|
| 123 |
try:
|
| 124 |
# التحقق من صحة IP
|
| 125 |
socket.inet_aton(ip)
|
| 126 |
+
peer_url = f"http://{ip}:PORT/run"
|
| 127 |
|
| 128 |
# فحص سريع
|
| 129 |
if self.check_dts_node(ip):
|
launcher.py
CHANGED
|
@@ -11,6 +11,7 @@ import subprocess
|
|
| 11 |
import argparse
|
| 12 |
import time
|
| 13 |
from pathlib import Path
|
|
|
|
| 14 |
|
| 15 |
def check_requirements():
|
| 16 |
"""فحص المتطلبات والاعتماديات"""
|
|
|
|
| 11 |
import argparse
|
| 12 |
import time
|
| 13 |
from pathlib import Path
|
| 14 |
+
from peer_discovery import PORT
|
| 15 |
|
| 16 |
def check_requirements():
|
| 17 |
"""فحص المتطلبات والاعتماديات"""
|
live_streaming.py
CHANGED
|
@@ -13,6 +13,7 @@ from datetime import datetime
|
|
| 13 |
from processor_manager import should_offload
|
| 14 |
from remote_executor import execute_remotely
|
| 15 |
from functools import wraps
|
|
|
|
| 16 |
|
| 17 |
logging.basicConfig(level=logging.INFO)
|
| 18 |
|
|
|
|
| 13 |
from processor_manager import should_offload
|
| 14 |
from remote_executor import execute_remotely
|
| 15 |
from functools import wraps
|
| 16 |
+
from peer_discovery import PORT
|
| 17 |
|
| 18 |
logging.basicConfig(level=logging.INFO)
|
| 19 |
|
load_balancer.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
# load_balancer.py
|
| 2 |
import peer_discovery, requests, time, smart_tasks, psutil, socket
|
|
|
|
| 3 |
|
| 4 |
def send(peer, func, *args, **kw):
|
| 5 |
try:
|
|
|
|
| 1 |
# load_balancer.py
|
| 2 |
import peer_discovery, requests, time, smart_tasks, psutil, socket
|
| 3 |
+
from peer_discovery import PORT
|
| 4 |
|
| 5 |
def send(peer, func, *args, **kw):
|
| 6 |
try:
|
node_client.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# ================================================================
|
| 3 |
+
# node_client.py – عميل تسجيل العُقدة في نظام AmalOffload
|
| 4 |
+
# ---------------------------------------------------------------
|
| 5 |
+
# • يختار منفذًا (من ENV أو من مجموعة PORTS).
|
| 6 |
+
# • يجلب عنوان الـ IP المحلي.
|
| 7 |
+
# • يحاول التسجيل في خادم سجلٍّ مركزي واحد تِلو الآخر،
|
| 8 |
+
# وعلى كل المنافذ، حتى ينجح.
|
| 9 |
+
# • عند النجاح يُرجع قائمة الأقران (Peers) من الخادم.
|
| 10 |
+
# ================================================================
|
| 11 |
+
|
| 12 |
+
import os
|
| 13 |
+
import socket
|
| 14 |
+
import time
|
| 15 |
+
import logging
|
| 16 |
+
import random
|
| 17 |
+
import requests
|
| 18 |
+
from typing import Iterable, Tuple, List
|
| 19 |
+
|
| 20 |
+
# ⬇️ منافذ مقترحة؛ يمكنك التعديل أو توليدها ديناميكيًا
|
| 21 |
+
DEFAULT_PORTS = {
|
| 22 |
+
7520, 7384, 9021, 6998, 5810, 9274,
|
| 23 |
+
8645, 7329, 7734, 8456, 6173, 7860,
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
# ⬇️ خوادم السجل الاحتياطية بالترتيب المفضَّل
|
| 27 |
+
DEFAULT_REGISTRY_SERVERS = [
|
| 28 |
+
"https://cv4790811.regru.cloud",
|
| 29 |
+
"https://amaloffload.onrender.com",
|
| 30 |
+
"https://osamabyc86-offload.hf.space",
|
| 31 |
+
"http://10.229.36.125",
|
| 32 |
+
"http://10.229.228.178",
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
logging.basicConfig(
|
| 36 |
+
level=logging.INFO,
|
| 37 |
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
| 38 |
+
datefmt="%H:%M:%S",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class NodeClient:
|
| 43 |
+
"""
|
| 44 |
+
عميل خفيف يعتني بالتسجيل المتكرِّر في خادم سجل مركزي.
|
| 45 |
+
يمكن استيراده في أي سكربت وتشغيله في خيط منفصل.
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
PORTs: Iterable[int] | None = None,
|
| 51 |
+
registry_servers: List[str] | None = None,
|
| 52 |
+
node_id: str | None = None,
|
| 53 |
+
):
|
| 54 |
+
self.PORTs = set(PORTs) if PORTs else DEFAULT_PORTS
|
| 55 |
+
self.registry_servers = list(registry_servers) if registry_servers else DEFAULT_REGISTRY_SERVERS
|
| 56 |
+
self.node_id = node_id or os.getenv("NODE_ID", socket.gethostname())
|
| 57 |
+
|
| 58 |
+
# مبدئيًّا اختَر منفذًا (أولوية للمتغيّر البيئي إن وُجد)
|
| 59 |
+
self.port: int = int(os.getenv("CPU_PORT", random.choice(list(self.PORTs))))
|
| 60 |
+
self.current_server_index: int | None = None
|
| 61 |
+
|
| 62 |
+
# -------------------------------------------------------------------------
|
| 63 |
+
@staticmethod
|
| 64 |
+
def get_local_ip() -> str:
|
| 65 |
+
"""يحاول معرفة أفضل عنوان IP محلي لاستخدامه في الشبكة."""
|
| 66 |
+
try:
|
| 67 |
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
| 68 |
+
# لا يهم أن ينجح الاتصال الفعلي، الهدف كشف IP واجهة الخروج
|
| 69 |
+
s.connect(("10.255.255.255", 1))
|
| 70 |
+
return s.getsockname()[0]
|
| 71 |
+
except Exception:
|
| 72 |
+
return "127.0.0.1"
|
| 73 |
+
|
| 74 |
+
def _register_once(self, server: str, port: int) -> List[str]:
|
| 75 |
+
"""مُحاولة واحدة للتسجيل؛ تُعيد peers أو ترفع استثناءً."""
|
| 76 |
+
payload = {
|
| 77 |
+
"node_id": self.node_id,
|
| 78 |
+
"ip": self.get_local_ip(),
|
| 79 |
+
"port": port,
|
| 80 |
+
}
|
| 81 |
+
resp = requests.post(f"{server}/register", json=payload, timeout=5)
|
| 82 |
+
resp.raise_for_status()
|
| 83 |
+
return resp.json() # توقّع أن الخادم يُرجع قائمة أقران
|
| 84 |
+
|
| 85 |
+
# -------------------------------------------------------------------------
|
| 86 |
+
def connect_until_success(self, retry_delay: int = 5) -> Tuple[str, List[str]]:
|
| 87 |
+
"""
|
| 88 |
+
يدور على جميع المنافذ والخوادم حتى ينجح التسجيل.
|
| 89 |
+
• عند النجاح يُرجع: (عنوان الخادم، قائمة الأقران)
|
| 90 |
+
• لا يرفع استثناءات؛ إمّا ينجح أو يستمر في المحاولة إلى ما لا نهاية.
|
| 91 |
+
"""
|
| 92 |
+
logging.info("🔄 بدء محاولات التسجيل للعقدة '%s'...", self.node_id)
|
| 93 |
+
while True:
|
| 94 |
+
for port in self.PORTs:
|
| 95 |
+
for idx, server in enumerate(self.registry_servers):
|
| 96 |
+
try:
|
| 97 |
+
peers = self._register_once(server, port)
|
| 98 |
+
# سجّل النجاح واحفظ المعلومات
|
| 99 |
+
self.port = port
|
| 100 |
+
self.current_server_index = idx
|
| 101 |
+
logging.info("✅ متصل: %s على المنفذ %s", server, port)
|
| 102 |
+
return server, peers
|
| 103 |
+
except Exception as e:
|
| 104 |
+
logging.debug("❌ %s:%s -> %s", server, port, e)
|
| 105 |
+
time.sleep(retry_delay) # انتظر قليلًا ثم أعد المحاولة
|
| 106 |
+
|
| 107 |
+
# -------------------------------------------------------------------------
|
| 108 |
+
def run_background(self) -> None:
|
| 109 |
+
"""
|
| 110 |
+
إطلاق التسجيل في خيط منفصل؛ مفيد إذا كنت تريد
|
| 111 |
+
إبقاء Main Thread للمهام الأخرى.
|
| 112 |
+
"""
|
| 113 |
+
import threading # استيراد متأخر لتفادي الحمل الزائد عند import
|
| 114 |
+
threading.Thread(target=self.connect_until_success, daemon=True).start()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# -----------------------------------------------------------------------------
|
| 118 |
+
if __name__ == "__main__":
|
| 119 |
+
"""
|
| 120 |
+
للتجربة المباشرة:
|
| 121 |
+
$ python node_client.py
|
| 122 |
+
"""
|
| 123 |
+
client = NodeClient()
|
| 124 |
+
server, peer_list = client.connect_until_success()
|
| 125 |
+
logging.info("🗂️ الأقران: %s", peer_list)
|
package-lock.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
CHANGED
|
@@ -42,9 +42,7 @@
|
|
| 42 |
"@radix-ui/react-toggle-group": "^1.1.3",
|
| 43 |
"@radix-ui/react-tooltip": "^1.2.0",
|
| 44 |
"@tanstack/react-query": "^5.60.5",
|
| 45 |
-
"
|
| 46 |
-
"@types/jsonwebtoken": "^9.0.9",
|
| 47 |
-
"bcryptjs": "^3.0.2",
|
| 48 |
"class-variance-authority": "^0.7.1",
|
| 49 |
"clsx": "^2.1.1",
|
| 50 |
"cmdk": "^1.1.1",
|
|
@@ -56,21 +54,17 @@
|
|
| 56 |
"express": "^4.21.2",
|
| 57 |
"express-session": "^1.18.1",
|
| 58 |
"framer-motion": "^11.13.1",
|
| 59 |
-
"i18next": "^25.2.0",
|
| 60 |
"input-otp": "^1.4.2",
|
| 61 |
-
"jsonwebtoken": "^9.0.2",
|
| 62 |
"lucide-react": "^0.453.0",
|
| 63 |
"memorystore": "^1.6.7",
|
|
|
|
| 64 |
"next-themes": "^0.4.6",
|
| 65 |
-
"node-cron": "^4.0.5",
|
| 66 |
-
"nodemailer": "^7.0.3",
|
| 67 |
"passport": "^0.7.0",
|
| 68 |
"passport-local": "^1.0.0",
|
| 69 |
"react": "^18.3.1",
|
| 70 |
"react-day-picker": "^8.10.1",
|
| 71 |
"react-dom": "^18.3.1",
|
| 72 |
"react-hook-form": "^7.55.0",
|
| 73 |
-
"react-i18next": "^15.5.1",
|
| 74 |
"react-icons": "^5.4.0",
|
| 75 |
"react-resizable-panels": "^2.1.7",
|
| 76 |
"recharts": "^2.15.2",
|
|
@@ -84,7 +78,7 @@
|
|
| 84 |
"zod-validation-error": "^3.4.0"
|
| 85 |
},
|
| 86 |
"devDependencies": {
|
| 87 |
-
"@replit/vite-plugin-cartographer": "^0.2.
|
| 88 |
"@replit/vite-plugin-runtime-error-modal": "^0.0.3",
|
| 89 |
"@tailwindcss/typography": "^0.5.15",
|
| 90 |
"@tailwindcss/vite": "^4.1.3",
|
|
@@ -100,12 +94,14 @@
|
|
| 100 |
"@vitejs/plugin-react": "^4.3.2",
|
| 101 |
"autoprefixer": "^10.4.20",
|
| 102 |
"drizzle-kit": "^0.30.4",
|
|
|
|
|
|
|
| 103 |
"esbuild": "^0.25.0",
|
| 104 |
"postcss": "^8.4.47",
|
| 105 |
"tailwindcss": "^3.4.17",
|
| 106 |
"tsx": "^4.19.1",
|
| 107 |
"typescript": "5.6.3",
|
| 108 |
-
"vite": "^5.4.
|
| 109 |
},
|
| 110 |
"optionalDependencies": {
|
| 111 |
"bufferutil": "^4.0.8"
|
|
|
|
| 42 |
"@radix-ui/react-toggle-group": "^1.1.3",
|
| 43 |
"@radix-ui/react-tooltip": "^1.2.0",
|
| 44 |
"@tanstack/react-query": "^5.60.5",
|
| 45 |
+
"bonjour-service": "^1.3.0",
|
|
|
|
|
|
|
| 46 |
"class-variance-authority": "^0.7.1",
|
| 47 |
"clsx": "^2.1.1",
|
| 48 |
"cmdk": "^1.1.1",
|
|
|
|
| 54 |
"express": "^4.21.2",
|
| 55 |
"express-session": "^1.18.1",
|
| 56 |
"framer-motion": "^11.13.1",
|
|
|
|
| 57 |
"input-otp": "^1.4.2",
|
|
|
|
| 58 |
"lucide-react": "^0.453.0",
|
| 59 |
"memorystore": "^1.6.7",
|
| 60 |
+
"nanoid": "^5.1.5",
|
| 61 |
"next-themes": "^0.4.6",
|
|
|
|
|
|
|
| 62 |
"passport": "^0.7.0",
|
| 63 |
"passport-local": "^1.0.0",
|
| 64 |
"react": "^18.3.1",
|
| 65 |
"react-day-picker": "^8.10.1",
|
| 66 |
"react-dom": "^18.3.1",
|
| 67 |
"react-hook-form": "^7.55.0",
|
|
|
|
| 68 |
"react-icons": "^5.4.0",
|
| 69 |
"react-resizable-panels": "^2.1.7",
|
| 70 |
"recharts": "^2.15.2",
|
|
|
|
| 78 |
"zod-validation-error": "^3.4.0"
|
| 79 |
},
|
| 80 |
"devDependencies": {
|
| 81 |
+
"@replit/vite-plugin-cartographer": "^0.2.7",
|
| 82 |
"@replit/vite-plugin-runtime-error-modal": "^0.0.3",
|
| 83 |
"@tailwindcss/typography": "^0.5.15",
|
| 84 |
"@tailwindcss/vite": "^4.1.3",
|
|
|
|
| 94 |
"@vitejs/plugin-react": "^4.3.2",
|
| 95 |
"autoprefixer": "^10.4.20",
|
| 96 |
"drizzle-kit": "^0.30.4",
|
| 97 |
+
"electron": "^37.1.0",
|
| 98 |
+
"electron-builder": "^26.0.12",
|
| 99 |
"esbuild": "^0.25.0",
|
| 100 |
"postcss": "^8.4.47",
|
| 101 |
"tailwindcss": "^3.4.17",
|
| 102 |
"tsx": "^4.19.1",
|
| 103 |
"typescript": "5.6.3",
|
| 104 |
+
"vite": "^5.4.19"
|
| 105 |
},
|
| 106 |
"optionalDependencies": {
|
| 107 |
"bufferutil": "^4.0.8"
|
peer_discovery.py
CHANGED
|
@@ -4,137 +4,95 @@ import socket
|
|
| 4 |
import threading
|
| 5 |
import time
|
| 6 |
import logging
|
| 7 |
-
|
| 8 |
import requests
|
| 9 |
from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser
|
| 10 |
|
| 11 |
-
#
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
PEERS = set() # مجموعة URLs للأقران (/run)
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
def get_local_ip():
|
| 23 |
-
# حاول الحصول على IP عام
|
| 24 |
try:
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
| 29 |
except Exception:
|
| 30 |
-
|
| 31 |
-
try:
|
| 32 |
-
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 33 |
-
s.connect(("8.8.8.8", 80))
|
| 34 |
-
return s.getsockname()[0]
|
| 35 |
-
except Exception:
|
| 36 |
-
return "127.0.0.1"
|
| 37 |
-
finally:
|
| 38 |
-
try:
|
| 39 |
-
s.close()
|
| 40 |
-
except Exception:
|
| 41 |
-
pass
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
info = ServiceInfo(
|
| 49 |
-
SERVICE,
|
| 50 |
-
f"{socket.gethostname()}.{SERVICE}",
|
| 51 |
-
addresses=[socket.inet_aton(local_ip)],
|
| 52 |
-
port=PORT,
|
| 53 |
-
properties={b'load': b'0'}
|
| 54 |
-
)
|
| 55 |
-
try:
|
| 56 |
-
zc.register_service(info)
|
| 57 |
-
print(f"✅ LAN service registered: {local_ip}:{PORT}")
|
| 58 |
-
except Exception as e:
|
| 59 |
-
print(f"❌ LAN registration failed: {e}")
|
| 60 |
-
|
| 61 |
-
# ❷ مستمع لاكتشاف أجهزة LAN عبر Zeroconf
|
| 62 |
-
class Listener:
|
| 63 |
-
def add_service(self, zc, t, name):
|
| 64 |
-
info = zc.get_service_info(t, name)
|
| 65 |
-
if info and info.addresses:
|
| 66 |
-
ip = socket.inet_ntoa(info.addresses[0])
|
| 67 |
-
peer_url = f"http://{ip}:{info.port}/run"
|
| 68 |
-
if peer_url not in PEERS:
|
| 69 |
-
PEERS.add(peer_url)
|
| 70 |
-
print(f"🔗 LAN peer discovered: {peer_url}")
|
| 71 |
-
def update_service(self, zc, t, name):
|
| 72 |
-
self.add_service(zc, t, name)
|
| 73 |
-
def remove_service(self, zc, t, name):
|
| 74 |
-
print(f"❌ LAN peer removed: {name}")
|
| 75 |
-
|
| 76 |
-
# حلقة اكتشاف LAN مستمرة
|
| 77 |
-
def discover_lan_loop():
|
| 78 |
-
zc = Zeroconf()
|
| 79 |
-
ServiceBrowser(zc, SERVICE, Listener())
|
| 80 |
-
print(f"🔍 Started LAN discovery for {SERVICE}")
|
| 81 |
-
while True:
|
| 82 |
-
time.sleep(5)
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
def
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
resp.raise_for_status()
|
| 92 |
-
peers_list = resp.json()
|
| 93 |
-
for p in peers_list:
|
| 94 |
-
peer_url = f"http://{p['ip']}:{p['port']}/run"
|
| 95 |
-
if peer_url not in PEERS:
|
| 96 |
-
PEERS.add(peer_url)
|
| 97 |
-
print(f"🌐 Registered and discovered central peer: {peer_url}")
|
| 98 |
-
except Exception as e:
|
| 99 |
-
print(f"❌ Central registration failed: {e}")
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
print("🔄 Central registry sync loop started")
|
| 105 |
-
while True:
|
| 106 |
-
try:
|
| 107 |
-
resp = requests.get(f"{CENTRAL_REGISTRY_URL}/peers", timeout=5)
|
| 108 |
-
resp.raise_for_status()
|
| 109 |
-
peers_list = resp.json()
|
| 110 |
-
for p in peers_list:
|
| 111 |
-
peer_url = f"http://{p['ip']}:{p['port']}/run"
|
| 112 |
-
if peer_url not in PEERS:
|
| 113 |
-
PEERS.add(peer_url)
|
| 114 |
-
print(f"🌐 Central peer discovered: {peer_url}")
|
| 115 |
-
except Exception as e:
|
| 116 |
-
print(f"⚠️ Fetch central peers failed: {e}")
|
| 117 |
-
time.sleep(300)
|
| 118 |
|
| 119 |
-
# 🚀 دالة الإدخال الرئيسيّة
|
| 120 |
def main():
|
| 121 |
-
|
| 122 |
-
print("🚀 Peer Discovery System starting...")
|
| 123 |
|
| 124 |
-
#
|
| 125 |
-
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
-
#
|
| 129 |
-
|
| 130 |
-
threading.Thread(target=fetch_central_loop, daemon=True).start()
|
| 131 |
|
| 132 |
-
# إبقاء السكربت قيد التشغيل
|
| 133 |
try:
|
| 134 |
while True:
|
| 135 |
-
|
|
|
|
| 136 |
except KeyboardInterrupt:
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
if __name__ == "__main__":
|
| 140 |
main()
|
|
|
|
| 4 |
import threading
|
| 5 |
import time
|
| 6 |
import logging
|
|
|
|
| 7 |
import requests
|
| 8 |
from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser
|
| 9 |
|
| 10 |
+
# إعداد السجلات
|
| 11 |
+
logging.basicConfig(level=logging.INFO)
|
| 12 |
+
logger = logging.getLogger(__name__)
|
|
|
|
| 13 |
|
| 14 |
+
# منفذ الخدمة (بدءاً من 1000 مع زيادة متسلسلة)
|
| 15 |
+
current_port = 1000
|
| 16 |
|
| 17 |
+
def get_sequential_port():
|
| 18 |
+
global current_port
|
| 19 |
+
port = current_port
|
| 20 |
+
current_port += 1
|
| 21 |
+
if current_port > 9999:
|
| 22 |
+
current_port = 1000
|
| 23 |
+
return port
|
| 24 |
+
|
| 25 |
+
PORT = "7520" and int(os.getenv("CPU_PORT", get_sequential_port()))
|
| 26 |
+
SERVICE = "_tasknode._tcp.local."
|
| 27 |
+
PEERS = set()
|
| 28 |
+
PEERS_INFO = {}
|
| 29 |
+
|
| 30 |
+
CENTRAL_REGISTRY_SERVERS = [
|
| 31 |
+
"https://cv4790811.regru.cloud",
|
| 32 |
+
"https://amaloffload.onrender.com",
|
| 33 |
+
"https://osamabyc86-offload.hf.space",
|
| 34 |
+
"https://osamabyc19866-omsd.hf.space",
|
| 35 |
+
"https://52.13.128.108",
|
| 36 |
+
"https://176.28.175.216",
|
| 37 |
+
"https://44.229.227.142",
|
| 38 |
+
"https://osamabyc86-amalcoreflow.hf.space"
|
| 39 |
+
]
|
| 40 |
|
| 41 |
def get_local_ip():
|
|
|
|
| 42 |
try:
|
| 43 |
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 44 |
+
s.connect(("8.8.8.8", 80))
|
| 45 |
+
ip = s.getsockname()[0]
|
| 46 |
+
s.close()
|
| 47 |
+
return ip
|
| 48 |
except Exception:
|
| 49 |
+
return "127.0.0.1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
+
def register_peer(ip, port):
|
| 52 |
+
peer_url = f"http://{ip}:{port}/run"
|
| 53 |
+
if peer_url not in PEERS:
|
| 54 |
+
PEERS.add(peer_url)
|
| 55 |
+
logger.info(f"تم تسجيل قرين جديد: {peer_url}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
def discover_lan_peers():
|
| 58 |
+
class Listener:
|
| 59 |
+
def add_service(self, zc, type_, name):
|
| 60 |
+
info = zc.get_service_info(type_, name)
|
| 61 |
+
if info:
|
| 62 |
+
ip = socket.inet_ntoa(info.addresses[0])
|
| 63 |
+
register_peer(ip, info.port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
zeroconf = Zeroconf()
|
| 66 |
+
ServiceBrowser(zeroconf, SERVICE, Listener())
|
| 67 |
+
return zeroconf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
|
|
|
| 69 |
def main():
|
| 70 |
+
logger.info("🚀 بدء نظام اكتشاف الأقران...")
|
|
|
|
| 71 |
|
| 72 |
+
# تسجيل الخدمة المحلية
|
| 73 |
+
zeroconf = Zeroconf()
|
| 74 |
+
info = ServiceInfo(
|
| 75 |
+
type_=SERVICE,
|
| 76 |
+
name=f"{socket.gethostname()}.{SERVICE}",
|
| 77 |
+
addresses=[socket.inet_aton(get_local_ip())],
|
| 78 |
+
port=int(PORT),
|
| 79 |
+
properties={b'version': b'1.0'},
|
| 80 |
+
server=f"{socket.gethostname()}.local."
|
| 81 |
+
)
|
| 82 |
+
zeroconf.register_service(info)
|
| 83 |
|
| 84 |
+
# بدء اكتشاف الأقران
|
| 85 |
+
discover_lan_peers()
|
|
|
|
| 86 |
|
|
|
|
| 87 |
try:
|
| 88 |
while True:
|
| 89 |
+
logger.info(f"عدد الأقران المكتشفين: {len(PEERS)}")
|
| 90 |
+
time.sleep(10)
|
| 91 |
except KeyboardInterrupt:
|
| 92 |
+
logger.info("🛑 إيقاف النظام...")
|
| 93 |
+
finally:
|
| 94 |
+
zeroconf.unregister_service(info)
|
| 95 |
+
zeroconf.close()
|
| 96 |
|
| 97 |
if __name__ == "__main__":
|
| 98 |
main()
|
peer_registry.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import socket
|
| 2 |
import time
|
| 3 |
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo
|
|
|
|
| 4 |
|
| 5 |
class Listener:
|
| 6 |
def __init__(self):
|
|
@@ -55,7 +56,7 @@ def discover_peers(timeout=2):
|
|
| 55 |
|
| 56 |
if __name__ == "__main__":
|
| 57 |
local_ip = socket.gethostbyname(socket.gethostname())
|
| 58 |
-
port =
|
| 59 |
|
| 60 |
zc = register_service(local_ip, port, load=0.1)
|
| 61 |
|
|
|
|
| 1 |
import socket
|
| 2 |
import time
|
| 3 |
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo
|
| 4 |
+
from peer_discovery import PORT
|
| 5 |
|
| 6 |
class Listener:
|
| 7 |
def __init__(self):
|
|
|
|
| 56 |
|
| 57 |
if __name__ == "__main__":
|
| 58 |
local_ip = socket.gethostbyname(socket.gethostname())
|
| 59 |
+
port = PORT
|
| 60 |
|
| 61 |
zc = register_service(local_ip, port, load=0.1)
|
| 62 |
|
peer_server.py
CHANGED
|
@@ -5,8 +5,20 @@ import psutil
|
|
| 5 |
import smart_tasks
|
| 6 |
import time
|
| 7 |
import socket
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
app = Flask(__name__) # إنشاء التطبيق
|
| 11 |
|
| 12 |
@app.route("/cpu")
|
|
@@ -33,5 +45,5 @@ def run():
|
|
| 33 |
return jsonify(error=str(e)), 500
|
| 34 |
|
| 35 |
if __name__ == "__main__": # التصحيح هنا
|
| 36 |
-
app.run(host="0.0.0.0", port=
|
| 37 |
|
|
|
|
| 5 |
import smart_tasks
|
| 6 |
import time
|
| 7 |
import socket
|
| 8 |
+
from peer_discovery import PORT
|
| 9 |
+
from flask import Flask
|
| 10 |
+
app = Flask(__name__)
|
| 11 |
+
@app.route("/run", methods=["POST"])
|
| 12 |
+
def run_task():
|
| 13 |
+
try:
|
| 14 |
+
data = request.get_json()
|
| 15 |
+
print("🔹 البيانات المستلمة:", data)
|
| 16 |
+
count = data.get("count", 100)
|
| 17 |
+
...
|
| 18 |
+
return jsonify(result)
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print("❌ خطأ أثناء التنفيذ:", e)
|
| 21 |
+
return jsonify({"error": str(e)}), 500
|
| 22 |
app = Flask(__name__) # إنشاء التطبيق
|
| 23 |
|
| 24 |
@app.route("/cpu")
|
|
|
|
| 45 |
return jsonify(error=str(e)), 500
|
| 46 |
|
| 47 |
if __name__ == "__main__": # التصحيح هنا
|
| 48 |
+
app.run(host="0.0.0.0", port=PORT)
|
| 49 |
|
quick_connection_test.py
CHANGED
|
@@ -13,7 +13,7 @@ def test_connection():
|
|
| 13 |
|
| 14 |
# 1. فحص الخادم المحلي
|
| 15 |
try:
|
| 16 |
-
response = requests.get("http://localhost:
|
| 17 |
if response.status_code == 200:
|
| 18 |
print("✅ الخادم المحلي يعمل")
|
| 19 |
else:
|
|
|
|
| 13 |
|
| 14 |
# 1. فحص الخادم المحلي
|
| 15 |
try:
|
| 16 |
+
response = requests.get("http://localhost:PORT/health", timeout=3)
|
| 17 |
if response.status_code == 200:
|
| 18 |
print("✅ الخادم المحلي يعمل")
|
| 19 |
else:
|
ram_manager.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ram_manager.py – Distributed RAM Offload Agent
|
| 3 |
+
================================================
|
| 4 |
+
|
| 5 |
+
❖ الغرض
|
| 6 |
+
------------
|
| 7 |
+
يوفِّر هذا الملف إضافة مستقلة إلى مشروع **AmalOffload** من أجل مشاركة الذاكرة (RAM) بين جميع العُقد التي تشغِّل المشروع. عندما ينخفض مقدار الذاكرة الحرة على إحدى العُقد إلى أقل من 2 جيجابايت (أو أي قيمة تحدّدها)، تُنقل كتل بيانات إلى عُقد أخرى ما تزال تملك ذاكرة حرّة.
|
| 8 |
+
|
| 9 |
+
❖ المزايا الرئيسة
|
| 10 |
+
------------------
|
| 11 |
+
* يراقب استهلاك الذاكرة محليًّا بانتظام.
|
| 12 |
+
* يعلن عن نفسه ويكتشف الأقران (Peers) تلقائيًّا بالاعتماد على `peer_registry` إن وُجد، أو على قائمة في المتغيّر البيئي `CENTRAL_PEERS` كحلّ احتياطي.
|
| 13 |
+
* يعرض واجهة HTTP بسيطة (`Flask`) بثلاث مسارات:
|
| 14 |
+
* `GET /ram_status` → يُرجِع مقدار الذاكرة الحرّة بالعُقدة.
|
| 15 |
+
* `POST /ram_store` → يستقبل كتلة بيانات (Base64) ويحجزها في الذاكرة.
|
| 16 |
+
* `GET /ram_fetch/<id>` → يُرجِع كتلة بيانات مُخزّنة بحسب معرّفها.
|
| 17 |
+
|
| 18 |
+
❖ طريقة التشغيل
|
| 19 |
+
----------------
|
| 20 |
+
```bash
|
| 21 |
+
python ram_manager.py --ram-limit 2048 --chunk 64 --interval 5
|
| 22 |
+
```
|
| 23 |
+
أو اكتفِ بالتشغيل بدون وسائط واعتمد على القيم الافتراضيّة أو على متغيّرات البيئة:
|
| 24 |
+
```bash
|
| 25 |
+
export RAM_THRESHOLD_MB=2048
|
| 26 |
+
export RAM_CHUNK_MB=64
|
| 27 |
+
python ram_manager.py
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
❖ التعليمات البرمجيّة
|
| 31 |
+
----------------------
|
| 32 |
+
"""
|
| 33 |
+
import os
|
| 34 |
+
import psutil
|
| 35 |
+
import time
|
| 36 |
+
import threading
|
| 37 |
+
import socket
|
| 38 |
+
import base64
|
| 39 |
+
import uuid
|
| 40 |
+
from typing import Dict, List
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
from flask import Flask, request, jsonify
|
| 44 |
+
except ImportError as exc:
|
| 45 |
+
raise RuntimeError("Flask غير مُثبّت. نفِّذ: pip install flask") from exc
|
| 46 |
+
|
| 47 |
+
# محاولة استيراد مُسجِّل الأقران الحالي من المشروع
|
| 48 |
+
try:
|
| 49 |
+
import peer_registry # يتوقع أن يحتوي على list_peers()
|
| 50 |
+
except ImportError:
|
| 51 |
+
peer_registry = None
|
| 52 |
+
|
| 53 |
+
# الإعدادات – قابلة للتعديل عبر متغيّرات البيئة
|
| 54 |
+
RAM_LIMIT_MB = int(os.getenv("RAM_THRESHOLD_MB", "2048")) # الحد الأدنى للرام الحرّة قبل الت offload
|
| 55 |
+
CHUNK_MB = int(os.getenv("RAM_CHUNK_MB", "64")) # حجم الكتلة المُرسَلة
|
| 56 |
+
CHECK_INTERVAL = int(os.getenv("RAM_CHECK_INTERVAL", "5")) # ثواني بين كل فحص
|
| 57 |
+
RAM_PORT = int(os.getenv("RAM_PORT", "8765")) # بورت واجهة الذاكرة
|
| 58 |
+
|
| 59 |
+
app = Flask(__name__)
|
| 60 |
+
|
| 61 |
+
# مخزن الكتل الواردة
|
| 62 |
+
remote_chunks: Dict[str, bytes] = {}
|
| 63 |
+
|
| 64 |
+
def get_free_ram_mb() -> int:
|
| 65 |
+
"""الذاكرة الحرّة بالميغابايت"""
|
| 66 |
+
return psutil.virtual_memory().available // (1024 * 1024)
|
| 67 |
+
|
| 68 |
+
# ─────────────────── واجهة HTTP ────────────────────
|
| 69 |
+
@app.route("/ram_status", methods=["GET"])
|
| 70 |
+
def ram_status():
|
| 71 |
+
"""إرجاع كميّة الذاكرة الحرّة بالعُقدة."""
|
| 72 |
+
return jsonify({"free_mb": get_free_ram_mb()})
|
| 73 |
+
|
| 74 |
+
@app.route("/ram_store", methods=["POST"])
|
| 75 |
+
def ram_store():
|
| 76 |
+
"""تلقّي كتلة بيانات وتخزينها."""
|
| 77 |
+
payload = request.get_json(force=True)
|
| 78 |
+
cid = payload["id"]
|
| 79 |
+
blob_b64= payload["data"]
|
| 80 |
+
remote_chunks[cid] = base64.b64decode(blob_b64)
|
| 81 |
+
return jsonify({"status": "stored", "id": cid})
|
| 82 |
+
|
| 83 |
+
@app.route("/ram_fetch/<cid>", methods=["GET"])
|
| 84 |
+
def ram_fetch(cid):
|
| 85 |
+
blob = remote_chunks.get(cid)
|
| 86 |
+
if blob is None:
|
| 87 |
+
return jsonify({"error": "not found"}), 404
|
| 88 |
+
return jsonify({"id": cid, "data": base64.b64encode(blob).decode()})
|
| 89 |
+
|
| 90 |
+
# ─────────────────── وظائف داخليّة ───────────────────
|
| 91 |
+
|
| 92 |
+
def start_api():
|
| 93 |
+
"""تشغيل خادم Flask في خيط منفصل."""
|
| 94 |
+
from werkzeug.serving import make_server
|
| 95 |
+
server = make_server("0.0.0.0", RAM_PORT, app)
|
| 96 |
+
server.serve_forever()
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def discover_peers() -> List[str]:
|
| 100 |
+
"""الحصول على قائمة IPs للأقران، باستثناء عنوان الجهاز الحالي."""
|
| 101 |
+
peers: List[str] = []
|
| 102 |
+
|
| 103 |
+
if peer_registry and hasattr(peer_registry, "list_peers"):
|
| 104 |
+
try:
|
| 105 |
+
peers_info = peer_registry.list_peers() # متوقع: [{"ip": "..."}, ...]
|
| 106 |
+
peers = [p["ip"] for p in peers_info if p.get("ip")]
|
| 107 |
+
except Exception:
|
| 108 |
+
pass
|
| 109 |
+
else:
|
| 110 |
+
central_env = os.getenv("CENTRAL_PEERS", "")
|
| 111 |
+
if central_env:
|
| 112 |
+
peers = central_env.split(",")
|
| 113 |
+
|
| 114 |
+
# إزالة عنوان الجهاز المحلي
|
| 115 |
+
try:
|
| 116 |
+
local_ip = socket.gethostbyname(socket.gethostname())
|
| 117 |
+
peers = [ip for ip in peers if ip != local_ip]
|
| 118 |
+
except Exception:
|
| 119 |
+
pass
|
| 120 |
+
|
| 121 |
+
return peers
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def offload_chunk(blob: bytes, peer_ip: str) -> bool:
|
| 125 |
+
"""إرسال كتلة بيانات إلى peer محدّد."""
|
| 126 |
+
import requests
|
| 127 |
+
try:
|
| 128 |
+
resp = requests.post(
|
| 129 |
+
f"http://{peer_ip}:{RAM_PORT}/ram_store",
|
| 130 |
+
json={"id": str(uuid.uuid4()), "data": base64.b64encode(blob).decode()},
|
| 131 |
+
timeout=5,
|
| 132 |
+
)
|
| 133 |
+
return resp.status_code == 200
|
| 134 |
+
except Exception:
|
| 135 |
+
return False
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def monitor_loop():
|
| 139 |
+
"""مراقبة الذاكرة واستدعاء offload عند الحاجة."""
|
| 140 |
+
while True:
|
| 141 |
+
free_mb = get_free_ram_mb()
|
| 142 |
+
if free_mb < RAM_LIMIT_MB:
|
| 143 |
+
peers = discover_peers()
|
| 144 |
+
if not peers:
|
| 145 |
+
print("[RAM] لا يوجد أقران متاحون حاليًا.")
|
| 146 |
+
else:
|
| 147 |
+
blob = bytes(CHUNK_MB * 1024 * 1024) # كتلة وهميّة – استبدلها ببيانات حقيقيّة عند الدمج
|
| 148 |
+
for ip in peers:
|
| 149 |
+
if offload_chunk(blob, ip):
|
| 150 |
+
print(f"[RAM] أرسلت {CHUNK_MB}MB إلى {ip}")
|
| 151 |
+
break
|
| 152 |
+
else:
|
| 153 |
+
print("[RAM] كل الأقران رفضوا التخزين أو حدث خطأ.")
|
| 154 |
+
time.sleep(CHECK_INTERVAL)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def main():
|
| 158 |
+
threading.Thread(target=start_api, daemon=True).start()
|
| 159 |
+
monitor_loop()
|
| 160 |
+
|
| 161 |
+
if __name__ == "__main__":
|
| 162 |
+
main()
|
remote_executor.py
CHANGED
|
@@ -12,11 +12,12 @@ from typing import Any
|
|
| 12 |
|
| 13 |
# قائمة الأقران (URLs) المستخرجة من peer_discovery
|
| 14 |
from peer_discovery import PEERS
|
|
|
|
| 15 |
|
| 16 |
# عنوان افتراضي احتياطي (يمكن تغييره بمتغير بيئي REMOTE_SERVER)
|
| 17 |
FALLBACK_SERVER = os.getenv(
|
| 18 |
"REMOTE_SERVER",
|
| 19 |
-
"http://89.111.171.92:
|
| 20 |
)
|
| 21 |
|
| 22 |
# محاولة استيراد SecurityManager (اختياري)
|
|
|
|
| 12 |
|
| 13 |
# قائمة الأقران (URLs) المستخرجة من peer_discovery
|
| 14 |
from peer_discovery import PEERS
|
| 15 |
+
from peer_discovery import PORT
|
| 16 |
|
| 17 |
# عنوان افتراضي احتياطي (يمكن تغييره بمتغير بيئي REMOTE_SERVER)
|
| 18 |
FALLBACK_SERVER = os.getenv(
|
| 19 |
"REMOTE_SERVER",
|
| 20 |
+
"http://89.111.171.92:PORT/run"
|
| 21 |
)
|
| 22 |
|
| 23 |
# محاولة استيراد SecurityManager (اختياري)
|
rpc_server.py
CHANGED
|
@@ -9,6 +9,7 @@ from flask import Flask, request, jsonify
|
|
| 9 |
import smart_tasks # «your_tasks» تمّ استيراده تحت هذا الاسم فى main.py
|
| 10 |
import logging, json
|
| 11 |
from security_layer import SecurityManager
|
|
|
|
| 12 |
|
| 13 |
SECURITY = SecurityManager("my_shared_secret_123")
|
| 14 |
|
|
@@ -69,5 +70,5 @@ def run():
|
|
| 69 |
|
| 70 |
# ------------------------------------------------------------------
|
| 71 |
if name == "main":
|
| 72 |
-
# تأكد أن المنفذ
|
| 73 |
-
app.run(host="0.0.0.0", port=
|
|
|
|
| 9 |
import smart_tasks # «your_tasks» تمّ استيراده تحت هذا الاسم فى main.py
|
| 10 |
import logging, json
|
| 11 |
from security_layer import SecurityManager
|
| 12 |
+
from peer_discovery import PORT
|
| 13 |
|
| 14 |
SECURITY = SecurityManager("my_shared_secret_123")
|
| 15 |
|
|
|
|
| 70 |
|
| 71 |
# ------------------------------------------------------------------
|
| 72 |
if name == "main":
|
| 73 |
+
# تأكد أن المنفذ PORT مفتوح
|
| 74 |
+
app.run(host="0.0.0.0", port=PORT)
|
security_layer.py
CHANGED
|
@@ -9,6 +9,7 @@ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
| 9 |
from cryptography.fernet import Fernet
|
| 10 |
import os, base64, json
|
| 11 |
from typing import Dict
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
class SecurityManager:
|
|
|
|
| 9 |
from cryptography.fernet import Fernet
|
| 10 |
import os, base64, json
|
| 11 |
from typing import Dict
|
| 12 |
+
from peer_discovery import PORT
|
| 13 |
|
| 14 |
|
| 15 |
class SecurityManager:
|
server.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
| 4 |
from your_tasks import *
|
| 5 |
from project_identifier import create_project_endpoint, get_project_info
|
| 6 |
import logging
|
|
|
|
| 7 |
|
| 8 |
app = Flask(__name__)
|
| 9 |
CORS(app)
|
|
@@ -21,7 +22,7 @@ def multiply():
|
|
| 21 |
|
| 22 |
@app.route('/health', methods=['GET'])
|
| 23 |
def health_check():
|
| 24 |
-
return jsonify({"status": "healthy", "port":
|
| 25 |
|
| 26 |
@app.route('/project_info', methods=['GET'])
|
| 27 |
def project_info():
|
|
@@ -29,4 +30,4 @@ def project_info():
|
|
| 29 |
|
| 30 |
if __name__ == "__main__":
|
| 31 |
# هذا العنوان يسمح بالاستماع على IP خارجي لتلقي الاتصالات من الإنترنت
|
| 32 |
-
app.run(host="0.0.0.0", port=
|
|
|
|
| 4 |
from your_tasks import *
|
| 5 |
from project_identifier import create_project_endpoint, get_project_info
|
| 6 |
import logging
|
| 7 |
+
from peer_discovery import PORT
|
| 8 |
|
| 9 |
app = Flask(__name__)
|
| 10 |
CORS(app)
|
|
|
|
| 22 |
|
| 23 |
@app.route('/health', methods=['GET'])
|
| 24 |
def health_check():
|
| 25 |
+
return jsonify({"status": "healthy", "port": PORT})
|
| 26 |
|
| 27 |
@app.route('/project_info', methods=['GET'])
|
| 28 |
def project_info():
|
|
|
|
| 30 |
|
| 31 |
if __name__ == "__main__":
|
| 32 |
# هذا العنوان يسمح بالاستماع على IP خارجي لتلقي الاتصالات من الإنترنت
|
| 33 |
+
app.run(host="0.0.0.0", port=PORT)
|
smart_tasks.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import math
|
| 2 |
import numpy as np
|
| 3 |
import time
|
|
|
|
| 4 |
|
| 5 |
def prime_calculation(n: int):
|
| 6 |
"""ترجع قائمة الأعداد الأوليّة حتى n مع عددها"""
|
|
@@ -135,4 +136,4 @@ def game_ai_processing(ai_agents_count, decision_complexity, game_state_size):
|
|
| 135 |
"total_operations": total_operations,
|
| 136 |
"processing_time": time.time() - start_time,
|
| 137 |
"server_processed": True
|
| 138 |
-
}
|
|
|
|
| 1 |
import math
|
| 2 |
import numpy as np
|
| 3 |
import time
|
| 4 |
+
from peer_discovery import PORT
|
| 5 |
|
| 6 |
def prime_calculation(n: int):
|
| 7 |
"""ترجع قائمة الأعداد الأوليّة حتى n مع عددها"""
|
|
|
|
| 136 |
"total_operations": total_operations,
|
| 137 |
"processing_time": time.time() - start_time,
|
| 138 |
"server_processed": True
|
| 139 |
+
}
|
system_check.py
CHANGED
|
@@ -7,6 +7,7 @@ import requests
|
|
| 7 |
import psutil
|
| 8 |
import threading
|
| 9 |
from offload_lib import discover_peers
|
|
|
|
| 10 |
|
| 11 |
def check_local_system():
|
| 12 |
"""فحص النظام المحلي"""
|
|
@@ -23,7 +24,7 @@ def check_server_running():
|
|
| 23 |
"""فحص إذا كان الخادم المحلي يعمل"""
|
| 24 |
print("🌐 فحص الخادم المحلي...")
|
| 25 |
try:
|
| 26 |
-
response = requests.get("http://localhost:
|
| 27 |
if response.status_code == 200:
|
| 28 |
print("✅ الخادم المحلي يعمل بشكل صحيح")
|
| 29 |
return True
|
|
|
|
| 7 |
import psutil
|
| 8 |
import threading
|
| 9 |
from offload_lib import discover_peers
|
| 10 |
+
from peer_discovery import PORT
|
| 11 |
|
| 12 |
def check_local_system():
|
| 13 |
"""فحص النظام المحلي"""
|
|
|
|
| 24 |
"""فحص إذا كان الخادم المحلي يعمل"""
|
| 25 |
print("🌐 فحص الخادم المحلي...")
|
| 26 |
try:
|
| 27 |
+
response = requests.get("http://localhost:PORT/health", timeout=3)
|
| 28 |
if response.status_code == 200:
|
| 29 |
print("✅ الخادم المحلي يعمل بشكل صحيح")
|
| 30 |
return True
|
system_tray.py
CHANGED
|
@@ -9,6 +9,7 @@ import threading
|
|
| 9 |
import requests
|
| 10 |
import webbrowser
|
| 11 |
from pathlib import Path
|
|
|
|
| 12 |
|
| 13 |
try:
|
| 14 |
import pystray
|
|
|
|
| 9 |
import requests
|
| 10 |
import webbrowser
|
| 11 |
from pathlib import Path
|
| 12 |
+
from peer_discovery import PORT
|
| 13 |
|
| 14 |
try:
|
| 15 |
import pystray
|
task_splitter.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
from typing import Dict, Any, List
|
| 3 |
import networkx as nx
|
| 4 |
import hashlib
|
|
|
|
| 5 |
|
| 6 |
class TaskSplitter:
|
| 7 |
def __init__(self):
|
|
|
|
| 2 |
from typing import Dict, Any, List
|
| 3 |
import networkx as nx
|
| 4 |
import hashlib
|
| 5 |
+
from peer_discovery import PORT
|
| 6 |
|
| 7 |
class TaskSplitter:
|
| 8 |
def __init__(self):
|
test_distributed_system.py
CHANGED
|
@@ -9,6 +9,7 @@ import psutil
|
|
| 9 |
import logging
|
| 10 |
from offload_lib import discover_peers, matrix_multiply, prime_calculation, data_processing
|
| 11 |
from your_tasks import complex_operation
|
|
|
|
| 12 |
|
| 13 |
# إعداد السجل
|
| 14 |
logging.basicConfig(
|
|
|
|
| 9 |
import logging
|
| 10 |
from offload_lib import discover_peers, matrix_multiply, prime_calculation, data_processing
|
| 11 |
from your_tasks import complex_operation
|
| 12 |
+
from peer_discovery import PORT
|
| 13 |
|
| 14 |
# إعداد السجل
|
| 15 |
logging.basicConfig(
|
test_monitor.py
CHANGED
|
@@ -7,6 +7,7 @@ import threading
|
|
| 7 |
import psutil
|
| 8 |
import signal
|
| 9 |
import sys
|
|
|
|
| 10 |
|
| 11 |
class TestMonitor:
|
| 12 |
def __init__(self):
|
|
|
|
| 7 |
import psutil
|
| 8 |
import signal
|
| 9 |
import sys
|
| 10 |
+
from peer_discovery import PORT
|
| 11 |
|
| 12 |
class TestMonitor:
|
| 13 |
def __init__(self):
|
video_processing.py
CHANGED
|
@@ -7,6 +7,7 @@ import logging
|
|
| 7 |
from functools import wraps
|
| 8 |
from processor_manager import should_offload
|
| 9 |
from remote_executor import execute_remotely
|
|
|
|
| 10 |
|
| 11 |
logging.basicConfig(level=logging.INFO)
|
| 12 |
|
|
|
|
| 7 |
from functools import wraps
|
| 8 |
from processor_manager import should_offload
|
| 9 |
from remote_executor import execute_remotely
|
| 10 |
+
from peer_discovery import PORT
|
| 11 |
|
| 12 |
logging.basicConfig(level=logging.INFO)
|
| 13 |
|
your_tasks.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import math
|
| 2 |
import numpy as np
|
| 3 |
from offload_lib import offload
|
|
|
|
| 4 |
|
| 5 |
# الدوال الأساسية (محليّة)
|
| 6 |
def prime_calculation(n: int):
|
|
|
|
| 1 |
import math
|
| 2 |
import numpy as np
|
| 3 |
from offload_lib import offload
|
| 4 |
+
from peer_discovery import PORT
|
| 5 |
|
| 6 |
# الدوال الأساسية (محليّة)
|
| 7 |
def prime_calculation(n: int):
|