هذا الالتزام موجود في:
2026-07-26 19:57:48 +03:00
التزام a6e65f3f37
29 ملفات معدلة مع 3538 إضافات و0 حذوفات

عرض الملف

@@ -0,0 +1,11 @@
.git
.gitignore
__pycache__
*.pyc
*.pyo
*.pyd
.env
.dockerignore
Dockerfile
docker-compose.yml
README.md

عرض الملف

@@ -0,0 +1,25 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies if required
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Install Python requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application files
COPY monitor.py index.html entrypoint.sh ./
COPY data.jsonl data.csv monitoring_data.csv ./
# Create symlink so dashboard.html also works without code duplication
RUN ln -sf index.html dashboard.html && chmod +x entrypoint.sh
# Expose HTTP dashboard port
EXPOSE 8080
# Run entrypoint script
ENTRYPOINT ["/app/entrypoint.sh"]

عرض الملف

@@ -0,0 +1,60 @@
# مراقب موقع mithal.space (Mithal Website Monitor & Dashboard)
تطبيق متكامل لمراقبة موقع `mithal.space` يجمع مقاييس الـ Uptime, Latency, SSL Certificate Expiry, DNS Resolution, و Search Latency بشكل مستمر ويعرضها على لوحة تحكم تفاعلية.
---
## 🚀 التشغيل عبر Docker Compose (الافتراضي والمستحسن)
قم بتشغيل الحاوية في الخلفية مع حفظ البيانات تلقائياً على الجهاز المستضيف:
```bash
docker compose up -d
```
افتخ المتصفح وانتقل إلى:
👉 **[http://localhost:8080](http://localhost:8080)**
لإيقاف الخدمة:
```bash
docker compose down
```
---
## 🐳 التشغيل المباشر عبر Docker (دون Compose)
### 1. بناء صورة الـ Docker:
```bash
docker build -t mithal-monitor .
```
### 2. تشغيل الحاوية مع حفظ البيانات:
```bash
docker run -d \
--name mithal-monitor-app \
-p 8080:8080 \
-v "$(pwd)/data.jsonl:/app/data.jsonl" \
-v "$(pwd)/data.csv:/app/data.csv" \
mithal-monitor
```
---
## 🛠 التشغيل المحلي بدون Docker
### 1. تثبيت المتطلبات:
```bash
pip install -r requirements.txt
```
### 2. تشغيل المراقبة:
```bash
python monitor.py
```
### 3. تشغيل لوحة التحكم:
```bash
python -m http.server 8080
```
تصفح **[http://localhost:8080](http://localhost:8080)**.

عرض الملف

@@ -0,0 +1,24 @@
timestamp,dns_time_ms,http_up,http_status_code,http_latency_ms,ssl_valid,ssl_days_remaining,ssl_expiry_date,search_up,search_status_code,search_latency_ms,error
2026-07-26T16:29:34.975347+00:00,7.38,True,200,1107.77,False,,,True,200,947.57,SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
2026-07-26T16:31:48.964707+00:00,24.95,True,200,1147.77,False,,,True,200,988.58,SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
2026-07-26T16:32:58.200316+00:00,8.97,True,200,6586.57,False,,,True,200,1298.52,SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
2026-07-26T16:34:00.920377+00:00,7.73,True,200,1153.76,False,,,True,200,1139.45,SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
2026-07-26T16:35:03.095644+00:00,8.06,True,200,1063.03,True,,,True,200,982.81,تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography
2026-07-26T16:36:06.934149+00:00,1.67,True,200,1306.19,True,,,True,200,1216.5,تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography
2026-07-26T16:37:10.770763+00:00,1.28,True,200,1358.48,True,,,True,200,1217.19,تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography
2026-07-26T16:40:13.186999+00:00,13.85,True,200,1177.05,True,50,2026-09-15,True,200,1001.08,
2026-07-26T16:40:35.147022+00:00,8.38,True,200,1001.0,True,50,2026-09-15,True,200,1051.0,
2026-07-26T16:41:37.737274+00:00,14.84,True,200,984.06,True,50,2026-09-15,True,200,1035.39,
2026-07-26T16:42:46.446648+00:00,1.15,True,200,1056.56,True,50,2026-09-15,True,200,2815.26,
2026-07-26T16:43:52.360311+00:00,1.09,True,200,2388.43,True,50,2026-09-15,True,200,1096.36,
2026-07-26T16:44:55.263746+00:00,0.97,True,200,945.49,True,50,2026-09-15,True,200,977.1,
2026-07-26T16:45:58.906118+00:00,0.91,True,200,978.28,True,50,2026-09-15,True,200,1013.73,
2026-07-26T16:47:05.160803+00:00,9.87,True,200,909.02,True,50,2026-09-15,True,200,917.93,
2026-07-26T16:51:32.327343+00:00,2.35,True,200,655.0,True,50,2026-09-15,True,200,658.37,
2026-07-26T16:52:34.256793+00:00,6.05,True,200,640.74,True,50,2026-09-15,True,200,631.15,
2026-07-26T16:53:39.970593+00:00,8.9,True,200,4007.5,True,50,2026-09-15,True,200,862.53,
2026-07-26T16:54:23.722444+00:00,6.02,True,200,1312.45,True,50,2026-09-15,True,200,1295.87,
2026-07-26T16:54:43.161904+00:00,7.25,True,200,1002.48,True,50,2026-09-15,True,200,840.82,
2026-07-26T16:55:26.868127+00:00,116.93,True,200,989.42,True,50,2026-09-15,True,200,989.09,
2026-07-26T16:55:45.089142+00:00,2.53,True,200,593.88,True,50,2026-09-15,True,200,675.95,
2026-07-26T16:56:48.649570+00:00,2.16,True,200,662.51,True,50,2026-09-15,True,200,2219.87,
1 timestamp dns_time_ms http_up http_status_code http_latency_ms ssl_valid ssl_days_remaining ssl_expiry_date search_up search_status_code search_latency_ms error
2 2026-07-26T16:29:34.975347+00:00 7.38 True 200 1107.77 False True 200 947.57 SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
3 2026-07-26T16:31:48.964707+00:00 24.95 True 200 1147.77 False True 200 988.58 SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
4 2026-07-26T16:32:58.200316+00:00 8.97 True 200 6586.57 False True 200 1298.52 SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
5 2026-07-26T16:34:00.920377+00:00 7.73 True 200 1153.76 False True 200 1139.45 SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)
6 2026-07-26T16:35:03.095644+00:00 8.06 True 200 1063.03 True True 200 982.81 تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography
7 2026-07-26T16:36:06.934149+00:00 1.67 True 200 1306.19 True True 200 1216.5 تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography
8 2026-07-26T16:37:10.770763+00:00 1.28 True 200 1358.48 True True 200 1217.19 تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography
9 2026-07-26T16:40:13.186999+00:00 13.85 True 200 1177.05 True 50 2026-09-15 True 200 1001.08
10 2026-07-26T16:40:35.147022+00:00 8.38 True 200 1001.0 True 50 2026-09-15 True 200 1051.0
11 2026-07-26T16:41:37.737274+00:00 14.84 True 200 984.06 True 50 2026-09-15 True 200 1035.39
12 2026-07-26T16:42:46.446648+00:00 1.15 True 200 1056.56 True 50 2026-09-15 True 200 2815.26
13 2026-07-26T16:43:52.360311+00:00 1.09 True 200 2388.43 True 50 2026-09-15 True 200 1096.36
14 2026-07-26T16:44:55.263746+00:00 0.97 True 200 945.49 True 50 2026-09-15 True 200 977.1
15 2026-07-26T16:45:58.906118+00:00 0.91 True 200 978.28 True 50 2026-09-15 True 200 1013.73
16 2026-07-26T16:47:05.160803+00:00 9.87 True 200 909.02 True 50 2026-09-15 True 200 917.93
17 2026-07-26T16:51:32.327343+00:00 2.35 True 200 655.0 True 50 2026-09-15 True 200 658.37
18 2026-07-26T16:52:34.256793+00:00 6.05 True 200 640.74 True 50 2026-09-15 True 200 631.15
19 2026-07-26T16:53:39.970593+00:00 8.9 True 200 4007.5 True 50 2026-09-15 True 200 862.53
20 2026-07-26T16:54:23.722444+00:00 6.02 True 200 1312.45 True 50 2026-09-15 True 200 1295.87
21 2026-07-26T16:54:43.161904+00:00 7.25 True 200 1002.48 True 50 2026-09-15 True 200 840.82
22 2026-07-26T16:55:26.868127+00:00 116.93 True 200 989.42 True 50 2026-09-15 True 200 989.09
23 2026-07-26T16:55:45.089142+00:00 2.53 True 200 593.88 True 50 2026-09-15 True 200 675.95
24 2026-07-26T16:56:48.649570+00:00 2.16 True 200 662.51 True 50 2026-09-15 True 200 2219.87

عرض الملف

@@ -0,0 +1,23 @@
{"timestamp": "2026-07-26T16:29:34.975347+00:00", "dns": {"time_ms": 7.38, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1107.77, "error": null}, "ssl": {"valid": false, "days_remaining": null, "expiry_date": null, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}, "search": {"up": true, "status_code": 200, "latency_ms": 947.57, "error": null}, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}
{"timestamp": "2026-07-26T16:31:48.964707+00:00", "dns": {"time_ms": 24.95, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1147.77, "error": null}, "ssl": {"valid": false, "days_remaining": null, "expiry_date": null, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}, "search": {"up": true, "status_code": 200, "latency_ms": 988.58, "error": null}, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}
{"timestamp": "2026-07-26T16:32:58.200316+00:00", "dns": {"time_ms": 8.97, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 6586.57, "error": null}, "ssl": {"valid": false, "days_remaining": null, "expiry_date": null, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}, "search": {"up": true, "status_code": 200, "latency_ms": 1298.52, "error": null}, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}
{"timestamp": "2026-07-26T16:34:00.920377+00:00", "dns": {"time_ms": 7.73, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1153.76, "error": null}, "ssl": {"valid": false, "days_remaining": null, "expiry_date": null, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}, "search": {"up": true, "status_code": 200, "latency_ms": 1139.45, "error": null}, "error": "SSL invalid: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)"}
{"timestamp": "2026-07-26T16:35:03.095644+00:00", "dns": {"time_ms": 8.06, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1063.03, "error": null}, "ssl": {"valid": true, "days_remaining": null, "expiry_date": null, "error": "تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography"}, "search": {"up": true, "status_code": 200, "latency_ms": 982.81, "error": null}, "error": "تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography"}
{"timestamp": "2026-07-26T16:36:06.934149+00:00", "dns": {"time_ms": 1.67, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1306.19, "error": null}, "ssl": {"valid": true, "days_remaining": null, "expiry_date": null, "error": "تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography"}, "search": {"up": true, "status_code": 200, "latency_ms": 1216.5, "error": null}, "error": "تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography"}
{"timestamp": "2026-07-26T16:37:10.770763+00:00", "dns": {"time_ms": 1.28, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1358.48, "error": null}, "ssl": {"valid": true, "days_remaining": null, "expiry_date": null, "error": "تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography"}, "search": {"up": true, "status_code": 200, "latency_ms": 1217.19, "error": null}, "error": "تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography"}
{"timestamp": "2026-07-26T16:40:13.186999+00:00", "dns": {"time_ms": 13.85, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1177.05, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 1001.08, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:40:35.147022+00:00", "dns": {"time_ms": 8.38, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1001.0, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 1051.0, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:41:37.737274+00:00", "dns": {"time_ms": 14.84, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 984.06, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 1035.39, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:42:46.446648+00:00", "dns": {"time_ms": 1.15, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1056.56, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 2815.26, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:43:52.360311+00:00", "dns": {"time_ms": 1.09, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 2388.43, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 1096.36, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:44:55.263746+00:00", "dns": {"time_ms": 0.97, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 945.49, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 977.1, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:45:58.906118+00:00", "dns": {"time_ms": 0.91, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 978.28, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 1013.73, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:47:05.160803+00:00", "dns": {"time_ms": 9.87, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 909.02, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 917.93, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:51:32.327343+00:00", "dns": {"time_ms": 2.35, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 655.0, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 658.37, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:52:34.256793+00:00", "dns": {"time_ms": 6.05, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 640.74, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 631.15, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:53:39.970593+00:00", "dns": {"time_ms": 8.9, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 4007.5, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 862.53, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:54:23.722444+00:00", "dns": {"time_ms": 6.02, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1312.45, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 1295.87, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:54:43.161904+00:00", "dns": {"time_ms": 7.25, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 1002.48, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 840.82, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:55:26.868127+00:00", "dns": {"time_ms": 116.93, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 989.42, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 989.09, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:55:45.089142+00:00", "dns": {"time_ms": 2.53, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 593.88, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 675.95, "error": null}, "error": null}
{"timestamp": "2026-07-26T16:56:48.649570+00:00", "dns": {"time_ms": 2.16, "error": null}, "http": {"up": true, "status_code": 200, "latency_ms": 662.51, "error": null}, "ssl": {"valid": true, "days_remaining": 50, "expiry_date": "2026-09-15", "error": null}, "search": {"up": true, "status_code": 200, "latency_ms": 2219.87, "error": null}, "error": null}

عرض الملف

@@ -0,0 +1,15 @@
#!/bin/sh
set -e
export PYTHONUNBUFFERED=1
# Ensure dashboard.html points to index.html to avoid code duplication
if [ ! -L dashboard.html ] && [ ! -f dashboard.html ]; then
ln -sf index.html dashboard.html
fi
echo "Starting Mithal website background monitor..."
python3 monitor.py &
echo "Starting Mithal web dashboard on port 8080..."
exec python3 -m http.server 8080

عرض الملف

@@ -0,0 +1,385 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mithal.space — لوحة المراقبة</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.4/chart.umd.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg: #0a0e17;
--panel: #121826;
--panel-border: #202838;
--text: #e7ebf3;
--muted: #7c8aa5;
--ok: #34d399;
--warn: #f2b84b;
--danger: #ef5a6f;
--accent: #4fd1ff;
--mono: 'JetBrains Mono', monospace;
--sans: 'Inter', sans-serif;
}
*{ box-sizing:border-box; }
body{
margin:0;
background: radial-gradient(1200px 600px at 80% -10%, #12203a 0%, var(--bg) 55%);
color: var(--text);
font-family: var(--sans);
min-height:100vh;
padding: 28px 20px 60px;
}
header{
display:flex; align-items:center; justify-content:space-between;
max-width:1180px; margin:0 auto 24px; flex-wrap:wrap; gap:12px;
}
.brand{ display:flex; align-items:center; gap:12px; }
.pulse-dot{
width:14px; height:14px; border-radius:50%;
background: var(--ok);
box-shadow: 0 0 0 0 rgba(52,211,153,0.6);
animation: pulse 1.8s infinite;
}
.pulse-dot.down{ background: var(--danger); box-shadow:0 0 0 0 rgba(239,90,111,0.6); animation: pulse-down 1.8s infinite; }
@keyframes pulse{
0%{ box-shadow: 0 0 0 0 rgba(52,211,153,0.55); }
70%{ box-shadow: 0 0 0 12px rgba(52,211,153,0); }
100%{ box-shadow: 0 0 0 0 rgba(52,211,153,0); }
}
@keyframes pulse-down{
0%{ box-shadow: 0 0 0 0 rgba(239,90,111,0.55); }
70%{ box-shadow: 0 0 0 12px rgba(239,90,111,0); }
100%{ box-shadow: 0 0 0 0 rgba(239,90,111,0); }
}
h1{ font-size:20px; margin:0; letter-spacing:0.2px; }
h1 span{ color: var(--muted); font-weight:400; font-size:14px; display:block; font-family: var(--mono); margin-top:2px; }
.controls{ display:flex; gap:8px; align-items:center; }
.controls label{
font-family: var(--mono); font-size:12px; color:var(--muted);
background: var(--panel); border:1px solid var(--panel-border);
padding:8px 12px; border-radius:8px; cursor:pointer;
transition: all 0.2s ease;
}
.controls label:hover{ background: #1a2234; color: var(--text); }
.controls input[type=file]{ display:none; }
.last-updated{ font-family: var(--mono); font-size:12px; color:var(--muted); }
main{ max-width:1180px; margin:0 auto; display:grid; gap:16px; grid-template-columns: repeat(12,1fr); }
.card{
background: var(--panel); border:1px solid var(--panel-border);
border-radius:14px; padding:18px 20px;
}
.card h2{
margin:0 0 10px; font-size:12px; text-transform:uppercase; letter-spacing:1px;
color: var(--muted); font-weight:600;
}
.metric{ font-family: var(--mono); font-size:34px; font-weight:700; line-height:1; }
.metric small{ font-size:13px; color:var(--muted); font-weight:400; margin-right:6px; }
.sub{ margin-top:8px; font-family: var(--mono); font-size:12px; color:var(--muted); word-break: break-word; }
.ok-text{ color:var(--ok); } .warn-text{ color:var(--warn); } .danger-text{ color:var(--danger); }
.col-4{ grid-column: span 4; }
.col-12{ grid-column: span 12; }
.col-6{ grid-column: span 6; }
@media (max-width: 800px){
.col-4, .col-6{ grid-column: span 12; }
}
canvas{ max-height:260px; width:100%; }
table{ width:100%; border-collapse:collapse; font-family: var(--mono); font-size:13px; }
th, td{ text-align:right; padding:9px 8px; border-bottom:1px solid var(--panel-border); }
th{ color:var(--muted); font-weight:500; font-size:11px; text-transform:uppercase; letter-spacing:0.5px; }
.badge{ display:inline-block; padding:2px 9px; border-radius:20px; font-size:11px; font-weight:700; }
.badge.up{ background: rgba(52,211,153,0.15); color:var(--ok); }
.badge.down{ background: rgba(239,90,111,0.15); color:var(--danger); }
.empty-state{
text-align:center; padding:60px 20px; color:var(--muted); font-family: var(--mono); font-size:13px;
}
.empty-state b{ color:var(--text); }
</style>
</head>
<body>
<header>
<div class="brand">
<div class="pulse-dot" id="pulseDot"></div>
<h1>mithal.space<span>لوحة مراقبة الموقع — Uptime · Latency · SSL · DNS · Search</span></h1>
</div>
<div class="controls">
<span class="last-updated" id="lastUpdated">لا توجد بيانات بعد</span>
<label for="fileInput">📂 تحميل data.jsonl</label>
<input type="file" id="fileInput" accept=".jsonl,.json,.txt">
</div>
</header>
<main id="dashboard">
<div class="card col-12" id="emptyCard" style="display: block;">
<div class="empty-state" id="emptyState">
شغّل <b>monitor.py</b> لجمع البيانات، ثم اخدم هذا المجلد عبر خادم محلي
(مثال: <b>python3 -m http.server 8080</b>) ليتم تحميل <b>data.jsonl</b> تلقائياً،
أو استخدم زر «تحميل data.jsonl» أعلاه لاختيار الملف يدوياً.
</div>
</div>
<div class="card col-4 main-content" style="display:none;">
<h2>Uptime (24 ساعة)</h2>
<div class="metric" id="uptimeMetric"></div>
<div class="sub" id="uptimeSub"></div>
</div>
<div class="card col-4 main-content" style="display:none;">
<h2>شهادة SSL</h2>
<div class="metric" id="sslMetric"></div>
<div class="sub" id="sslSub"></div>
</div>
<div class="card col-4 main-content" style="display:none;">
<h2>DNS (متوسط 24س) / آخر بحث</h2>
<div class="metric" id="dnsMetric"></div>
<div class="sub" id="dnsSub"></div>
</div>
<div class="card col-12 main-content" style="display:none;">
<h2>زمن الاستجابة — آخر ساعة (Latency)</h2>
<canvas id="latencyChart"></canvas>
</div>
<div class="card col-12 main-content" style="display:none;">
<h2>سجل آخر 10 فحوصات</h2>
<table>
<thead>
<tr><th>الوقت</th><th>الحالة</th><th>Latency</th><th>DNS</th><th>SSL (أيام)</th><th>Search</th></tr>
</thead>
<tbody id="logBody"></tbody>
</table>
</div>
</main>
<script>
const REFRESH_MS = 60000; // إعادة تحميل تلقائي كل دقيقة
let records = [];
let latencyChart = null;
function parseJsonl(text){
return text.split('\n')
.map(l => l.trim())
.filter(Boolean)
.map(l => { try { return JSON.parse(l); } catch(e){ return null; } })
.filter(Boolean);
}
function fmtTime(iso){
if (!iso) return '—';
const d = new Date(iso);
if (isNaN(d.getTime())) return iso;
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
return `${d.toLocaleDateString('ar-EG')} ${hours}:${minutes}:${seconds}`;
}
function fmtShortTime(iso){
if (!iso) return '—';
const d = new Date(iso);
if (isNaN(d.getTime())) return iso;
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
function statusColorClass(days){
if (days === null || days === undefined) return 'danger-text';
if (days <= 7) return 'danger-text';
if (days <= 30) return 'warn-text';
return 'ok-text';
}
function initChart() {
const chartCanvas = document.getElementById('latencyChart');
if (!chartCanvas) return;
const ctx = chartCanvas.getContext('2d');
latencyChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'HTTP latency (ms)',
data: [],
borderColor: '#4fd1ff',
backgroundColor: 'rgba(79,209,255,0.08)',
tension: 0.3,
pointRadius: 2,
spanGaps: true,
fill: true,
},
{
label: 'Search latency (ms)',
data: [],
borderColor: '#f2b84b',
backgroundColor: 'rgba(242,184,75,0.06)',
tension: 0.3,
pointRadius: 2,
spanGaps: true,
fill: true,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#7c8aa5', font: { family: 'JetBrains Mono', size: 11 } } }
},
scales: {
x: { ticks: { color: '#7c8aa5', maxTicksLimit: 12 }, grid: { color: '#202838' } },
y: { ticks: { color: '#7c8aa5' }, grid: { color: '#202838' }, title:{display:true, text:'ms', color:'#7c8aa5'} }
}
}
});
}
function render(){
const emptyCard = document.getElementById('emptyCard');
const mainCards = document.querySelectorAll('.main-content');
if (!records.length){
if (emptyCard) emptyCard.style.display = 'block';
mainCards.forEach(c => c.style.display = 'none');
return;
}
if (emptyCard) emptyCard.style.display = 'none';
mainCards.forEach(c => c.style.display = 'block');
const sorted = [...records].sort((a,b) => new Date(a.timestamp) - new Date(b.timestamp));
const latest = sorted[sorted.length - 1];
const nowMs = new Date(latest.timestamp).getTime();
// --- Uptime % over last 24h ---
const last24h = sorted.filter(r => nowMs - new Date(r.timestamp).getTime() <= 24*3600*1000);
const upCount = last24h.filter(r => r.http && r.http.up).length;
const uptimePct = last24h.length ? ((upCount / last24h.length) * 100).toFixed(2) : 'N/A';
const uptimeClass = uptimePct === 'N/A' ? '' : (uptimePct >= 99.5 ? 'ok-text' : uptimePct >= 95 ? 'warn-text' : 'danger-text');
const uptimeEl = document.getElementById('uptimeMetric');
if (uptimeEl) {
uptimeEl.className = `metric ${uptimeClass}`;
uptimeEl.innerHTML = `${uptimePct}${uptimePct !== 'N/A' ? '<small>%</small>' : ''}`;
}
const uptimeSubEl = document.getElementById('uptimeSub');
if (uptimeSubEl) {
uptimeSubEl.textContent = `${upCount}/${last24h.length} فحص ناجح آخر 24 ساعة`;
}
// --- SSL ---
const ssl = latest.ssl || {};
const hasValidDays = ssl.valid && ssl.days_remaining !== null && ssl.days_remaining !== undefined;
const sslClass = hasValidDays ? statusColorClass(ssl.days_remaining) : 'danger-text';
const sslMetricText = hasValidDays ? ssl.days_remaining : '—';
const sslSubText = hasValidDays ? (`تنتهي في ${ssl.expiry_date}`) : (ssl.error ? `غير سارية (${ssl.error})` : 'غير سارية أو تعذّر التحقق');
const sslMetricEl = document.getElementById('sslMetric');
if (sslMetricEl) {
sslMetricEl.className = `metric ${sslClass}`;
sslMetricEl.innerHTML = `${sslMetricText}${hasValidDays ? '<small>يوم متبقي</small>' : ''}`;
}
const sslSubEl = document.getElementById('sslSub');
if (sslSubEl) {
sslSubEl.textContent = sslSubText;
}
// --- DNS avg over last 24h ---
const dnsTimes = last24h.map(r => r.dns && r.dns.time_ms).filter(v => v !== null && v !== undefined);
const dnsAvg = dnsTimes.length ? (dnsTimes.reduce((a,b)=>a+b,0)/dnsTimes.length).toFixed(1) : 'N/A';
const dnsMetricEl = document.getElementById('dnsMetric');
if (dnsMetricEl) {
dnsMetricEl.innerHTML = `${dnsAvg}${dnsAvg !== 'N/A' ? '<small>ms</small>' : ''}`;
}
const dnsSubEl = document.getElementById('dnsSub');
if (dnsSubEl) {
dnsSubEl.textContent = `زمن البحث الأخير: ${latest.search && latest.search.latency_ms !== null && latest.search.latency_ms !== undefined ? latest.search.latency_ms + ' ms' : 'فشل الطلب'}`;
}
// --- pulse dot & last updated ---
const dot = document.getElementById('pulseDot');
if (dot) dot.classList.toggle('down', !latest.http || !latest.http.up);
const lastUpdatedEl = document.getElementById('lastUpdated');
if (lastUpdatedEl) lastUpdatedEl.textContent = 'آخر تحديث: ' + fmtTime(latest.timestamp);
// --- update Chart smoothly ---
const lastHour = sorted.filter(r => nowMs - new Date(r.timestamp).getTime() <= 3600*1000);
const chartSource = lastHour.length ? lastHour : sorted.slice(-30);
const labels = chartSource.map(r => fmtShortTime(r.timestamp));
const httpData = chartSource.map(r => r.http ? r.http.latency_ms : null);
const searchData = chartSource.map(r => r.search ? r.search.latency_ms : null);
if (!latencyChart) {
initChart();
}
if (latencyChart) {
latencyChart.data.labels = labels;
latencyChart.data.datasets[0].data = httpData;
latencyChart.data.datasets[1].data = searchData;
latencyChart.update();
}
// --- log table (last 10) ---
const logBody = document.getElementById('logBody');
if (logBody) {
const last10 = sorted.slice(-10).reverse();
logBody.innerHTML = last10.map(r => {
const rSslValid = r.ssl && r.ssl.valid && r.ssl.days_remaining !== null && r.ssl.days_remaining !== undefined;
return `
<tr>
<td>${fmtTime(r.timestamp)}</td>
<td><span class="badge ${r.http && r.http.up ? 'up' : 'down'}">${r.http && r.http.up ? 'UP' : 'DOWN'}</span></td>
<td>${r.http && r.http.latency_ms !== null && r.http.latency_ms !== undefined ? r.http.latency_ms + ' ms' : '—'}</td>
<td>${r.dns && r.dns.time_ms !== null && r.dns.time_ms !== undefined ? r.dns.time_ms + ' ms' : '—'}</td>
<td class="${statusColorClass(rSslValid ? r.ssl.days_remaining : null)}">${rSslValid ? r.ssl.days_remaining : '—'}</td>
<td>${r.search && r.search.latency_ms !== null && r.search.latency_ms !== undefined ? r.search.latency_ms + ' ms' : '—'}</td>
</tr>
`;
}).join('');
}
}
async function loadFromServer(){
try{
const res = await fetch('data.jsonl', { cache: 'no-store' });
if (!res.ok) throw new Error('not found');
const text = await res.text();
records = parseJsonl(text);
render();
} catch(e){
// Fallback when served directly as file:// or if fetch fails
}
}
document.getElementById('fileInput').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (evt) => {
records = parseJsonl(evt.target.result);
render();
};
reader.readAsText(file);
});
initChart();
loadFromServer();
setInterval(loadFromServer, REFRESH_MS);
</script>
</body>
</html>

عرض الملف

@@ -0,0 +1,327 @@
#!/usr/bin/env python3
"""
مراقب موقع mithal.space (Website Monitor)
==========================================
يجمع كل دقيقة:
- Latency : زمن استجابة HTTP للصفحة الرئيسية
- Uptime : هل الموقع متاح (status code 2xx/3xx)
- SSL : صلاحية الشهادة وعدد الأيام المتبقية على انتهائها
- DNS : زمن تحليل اسم النطاق (DNS resolution time)
- Search : زمن الرد على طلب بحث (search query) داخل الموقع
يخزّن كل فحص كسطر JSON مستقل في data.jsonl (JSON Lines) وأيضاً كصف
في data.csv، بحيث يمكن قراءة الملف تراكمياً دون الحاجة لإعادة كتابته.
الاستخدام:
python3 monitor.py # يعمل باستمرار، فحص كل 60 ثانية
python3 monitor.py --once # فحص واحد فقط (مناسب لجدولة cron)
python3 monitor.py --interval 30 # تغيير الفاصل الزمني (بالثواني)
"""
import argparse
import concurrent.futures
import csv
import json
import os
import socket
import ssl
import sys
import time
from datetime import datetime, timezone
import requests
# ----------------------------------------------------------------------------
# الإعدادات (Configuration) — عدّل هذه القيم حسب موقعك
# ----------------------------------------------------------------------------
SITE_URL = "https://mithal.space"
SITE_HOST = "mithal.space"
SITE_PORT = 443
# رابط البحث الذي سيتم اختباره — عدّله ليطابق مسار البحث الفعلي في موقعك
# مثال: https://mithal.space/search?q=test
SEARCH_URL = "https://mithal.space/search?q=test"
REQUEST_TIMEOUT = 10 # ثانية
CHECK_INTERVAL_SECONDS = 60 # كل دقيقة
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
JSONL_PATH = os.path.join(DATA_DIR, "data.jsonl")
CSV_PATH = os.path.join(DATA_DIR, "data.csv")
CSV_FIELDS = [
"timestamp",
"dns_time_ms",
"http_up",
"http_status_code",
"http_latency_ms",
"ssl_valid",
"ssl_days_remaining",
"ssl_expiry_date",
"search_up",
"search_status_code",
"search_latency_ms",
"error",
]
def now_iso():
return datetime.now(timezone.utc).isoformat()
# ----------------------------------------------------------------------------
# 1) DNS — وقت تحليل اسم النطاق
# ----------------------------------------------------------------------------
def measure_dns(hostname: str, timeout: float = REQUEST_TIMEOUT):
"""
ملاحظة مهمة: socket.getaddrinfo() لا يقبل معامل timeout أصلاً، وقد يتجمّد
إلى ما لا نهاية إذا كان الـ DNS بطيئاً أو محجوباً (VPN/جدار حماية/شبكة شركة).
لتفادي تجميد السكربت بالكامل، ننفّذ البحث في Thread منفصل ونفرض عليه
مهلة زمنية يدوياً عبر future.result(timeout=...).
"""
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
start = time.perf_counter()
try:
future = executor.submit(socket.getaddrinfo, hostname, None)
future.result(timeout=timeout)
elapsed_ms = (time.perf_counter() - start) * 1000
return {"time_ms": round(elapsed_ms, 2), "error": None}
except concurrent.futures.TimeoutError:
return {"time_ms": None, "error": f"DNS timeout after {timeout}s (تحقق من الاتصال بالإنترنت أو VPN/DNS)"}
except socket.gaierror as e:
return {"time_ms": None, "error": str(e)}
finally:
# wait=False حتى لا ننتظر انتهاء الـ thread العالق في حال حدوث timeout
executor.shutdown(wait=False)
# ----------------------------------------------------------------------------
# 2) HTTP — Latency + Uptime (status code)
# ----------------------------------------------------------------------------
def measure_http(url: str):
try:
start = time.perf_counter()
resp = requests.get(url, timeout=REQUEST_TIMEOUT)
elapsed_ms = (time.perf_counter() - start) * 1000
return {
"up": resp.status_code < 400,
"status_code": resp.status_code,
"latency_ms": round(elapsed_ms, 2),
"error": None,
}
except requests.exceptions.RequestException as e:
return {"up": False, "status_code": None, "latency_ms": None, "error": str(e)}
# ----------------------------------------------------------------------------
# 3) SSL — حالة الشهادة وتاريخ الانتهاء
# ----------------------------------------------------------------------------
def _get_ca_context():
"""يستخدم حزمة CA من certifi إن كانت مثبتة (أدق وأكثر استقراراً على ويندوز
من الاعتماد على مخزن الشهادات الافتراضي)، وإلا يعود للسياق الافتراضي."""
try:
import certifi
return ssl.create_default_context(cafile=certifi.where())
except ImportError:
return ssl.create_default_context()
def _read_cert_expiry(der_bytes: bytes):
"""Read certificate expiry date using Python's built-in SSL module."""
try:
pem_bytes = ssl.DER_cert_to_PEM_cert(der_bytes)
# ssl._ssl._test_decode_cert() needs a file,
# so we create a temporary certificate file.
import tempfile
with tempfile.NamedTemporaryFile(
mode="w",
suffix=".pem",
delete=False
) as f:
f.write(pem_bytes)
cert_path = f.name
try:
cert_info = ssl._ssl._test_decode_cert(cert_path)
expiry_str = cert_info.get("notAfter")
if expiry_str:
expiry_dt = datetime.strptime(
expiry_str,
"%b %d %H:%M:%S %Y %Z"
).replace(tzinfo=timezone.utc)
return expiry_dt
return None
finally:
os.unlink(cert_path)
except Exception:
return None
def check_ssl(hostname: str, port: int = 443):
"""
يفصل بين أمرين مختلفين قد يُخلط بينهما:
1) trusted : هل تثق سلسلة الشهادات (chain of trust) بالشهادة عبر حزمة
CA؟ قد تفشل هذه الخطوة محلياً (خصوصاً على ويندوز) دون أن
يكون هناك أي خلل في شهادة الموقع نفسه.
2) expiry : تاريخ انتهاء الشهادة الفعلي — نقرأه دائماً من البايتات
الخام للشهادة (getpeercert(binary_form=True)) بغض النظر
عن نجاح التحقق من الثقة، لأن getpeercert() العادي يعيد
قاموساً فارغاً {} إن لم يتم التحقق من الثقة.
"""
trusted = False
trust_error = None
days_remaining = None
expiry_date = None
# 1) محاولة اتصال بالتحقق الكامل من الثقة
try:
context = _get_ca_context()
with socket.create_connection((hostname, port), timeout=REQUEST_TIMEOUT) as sock:
with context.wrap_socket(sock, server_hostname=hostname):
trusted = True
except ssl.SSLCertVerificationError as e:
trust_error = f"فشل التحقق من سلسلة الثقة محلياً (قد يكون خلل CA bundle وليس عيباً بالشهادة): {e}"
except (socket.timeout, socket.gaierror, ConnectionRefusedError, OSError) as e:
return {"valid": False, "days_remaining": None, "expiry_date": None, "error": str(e)}
# 2) قراءة تاريخ الانتهاء بشكل مستقل، حتى لو فشلت خطوة الثقة أعلاه
try:
unverified_context = ssl._create_unverified_context()
with socket.create_connection((hostname, port), timeout=REQUEST_TIMEOUT) as sock:
with unverified_context.wrap_socket(sock, server_hostname=hostname) as ssock:
der_bytes = ssock.getpeercert(binary_form=True)
expiry_dt = _read_cert_expiry(der_bytes) if der_bytes else None
if expiry_dt is not None:
days_remaining = (expiry_dt - datetime.now(timezone.utc)).days
expiry_date = expiry_dt.date().isoformat()
elif trust_error is None:
trust_error = "تعذّر قراءة تاريخ الانتهاء: ثبّت حزمة cryptography عبر: pip install cryptography"
except (socket.timeout, socket.gaierror, ConnectionRefusedError, OSError) as e:
if trust_error is None:
trust_error = str(e)
return {
"valid": trusted,
"days_remaining": days_remaining,
"expiry_date": expiry_date,
"error": trust_error,
}
# ----------------------------------------------------------------------------
# 4) Search Response — زمن الرد على طلب بحث
# ----------------------------------------------------------------------------
def measure_search(url: str):
try:
start = time.perf_counter()
resp = requests.get(url, timeout=REQUEST_TIMEOUT)
elapsed_ms = (time.perf_counter() - start) * 1000
return {
"up": resp.status_code < 400,
"status_code": resp.status_code,
"latency_ms": round(elapsed_ms, 2),
"error": None,
}
except requests.exceptions.RequestException as e:
return {"up": False, "status_code": None, "latency_ms": None, "error": str(e)}
# ----------------------------------------------------------------------------
# تجميع كل المقاييس في فحص واحد
# ----------------------------------------------------------------------------
def collect_metrics():
dns = measure_dns(SITE_HOST)
http = measure_http(SITE_URL)
ssl_info = check_ssl(SITE_HOST, SITE_PORT)
search = measure_search(SEARCH_URL)
errors = [e for e in (dns.get("error"), http.get("error"), ssl_info.get("error"), search.get("error")) if e]
return {
"timestamp": now_iso(),
"dns": dns,
"http": http,
"ssl": ssl_info,
"search": search,
"error": "; ".join(errors) if errors else None,
}
# ----------------------------------------------------------------------------
# التخزين: JSON Lines + CSV
# ----------------------------------------------------------------------------
def save_jsonl(record: dict):
with open(JSONL_PATH, "a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
def save_csv(record: dict):
file_exists = os.path.isfile(CSV_PATH)
row = {
"timestamp": record["timestamp"],
"dns_time_ms": record["dns"].get("time_ms"),
"http_up": record["http"].get("up"),
"http_status_code": record["http"].get("status_code"),
"http_latency_ms": record["http"].get("latency_ms"),
"ssl_valid": record["ssl"].get("valid"),
"ssl_days_remaining": record["ssl"].get("days_remaining"),
"ssl_expiry_date": record["ssl"].get("expiry_date"),
"search_up": record["search"].get("up"),
"search_status_code": record["search"].get("status_code"),
"search_latency_ms": record["search"].get("latency_ms"),
"error": record.get("error"),
}
with open(CSV_PATH, "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=CSV_FIELDS)
if not file_exists:
writer.writeheader()
writer.writerow(row)
def run_once():
record = collect_metrics()
save_jsonl(record)
save_csv(record)
status = "UP" if record["http"]["up"] else "DOWN"
print(
f"[{record['timestamp']}] HTTP={status} "
f"latency={record['http']['latency_ms']}ms "
f"dns={record['dns']['time_ms']}ms "
f"ssl_days_left={record['ssl']['days_remaining']} "
f"search={record['search']['latency_ms']}ms",
flush=True,
)
return record
def main():
parser = argparse.ArgumentParser(description="مراقب موقع mithal.space")
parser.add_argument("--once", action="store_true", help="فحص واحد فقط ثم الخروج (مناسب لـ cron)")
parser.add_argument(
"--interval", type=int, default=CHECK_INTERVAL_SECONDS, help="الفاصل الزمني بالثواني بين الفحوصات"
)
args = parser.parse_args()
if args.once:
run_once()
return
print(f"بدء المراقبة المستمرة لـ {SITE_URL} كل {args.interval} ثانية... (Ctrl+C للإيقاف)", flush=True)
while True:
try:
run_once()
except Exception as e:
# لا نسمح لأي خطأ غير متوقع بإيقاف الحلقة الرئيسية
print(f"خطأ غير متوقع أثناء الفحص: {e}", flush=True)
time.sleep(args.interval)
if __name__ == "__main__":
main()

عرض الملف

@@ -0,0 +1,2 @@
timestamp,status_code,latency_ms,uptime,ssl_valid,ssl_expiry,ssl_days_remaining,ssl_check_latency_ms,dns_success,dns_latency_ms,search_status_code,search_latency_ms,search_success
2026-07-26T16:24:48.432979+00:00,200,1240.09,True,False,,,,True,0.6,200,1003.56,True
1 timestamp status_code latency_ms uptime ssl_valid ssl_expiry ssl_days_remaining ssl_check_latency_ms dns_success dns_latency_ms search_status_code search_latency_ms search_success
2 2026-07-26T16:24:48.432979+00:00 200 1240.09 True False True 0.6 200 1003.56 True

عرض الملف

@@ -0,0 +1,3 @@
requests>=2.28.0
certifi>=2023.0.0
cryptography>=40.0.0