welcome ghaymah
هذا الالتزام موجود في:
17
q5-mithal-monitor/Dockerfile
Normal file
17
q5-mithal-monitor/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# استخدام صورة بايثون خفيفة
|
||||
FROM python:3.9-slim
|
||||
|
||||
# تحديد مجلد العمل داخل الحاوية
|
||||
WORKDIR /app
|
||||
|
||||
# تثبيت المكاتب المطلوبة مباشرة بدون الحاجة لملف requirements.txt منفصل
|
||||
RUN pip install --no-cache-dir fastapi uvicorn requests
|
||||
|
||||
# نسخ باقي ملفات المشروع (مثل monitor.py و dashboard.html) إلى مجلد العمل
|
||||
COPY . /app
|
||||
|
||||
# فتح البورت الذي يعمل عليه التطبيق
|
||||
EXPOSE 8000
|
||||
|
||||
# أمر التشغيل المباشر باستخدام Uvicorn
|
||||
CMD ["uvicorn", "monitor:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
149
q5-mithal-monitor/dashboard.html
Normal file
149
q5-mithal-monitor/dashboard.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>لوحة مراقبة موقع مثال - Mithal Monitor</title>
|
||||
<!-- مكتبة الرسم البياني Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #0f172a; margin: 0; padding: 40px 20px; color: #ffffff; }
|
||||
.container { max-width: 800px; margin: auto; text-align: center; }
|
||||
h1 { color: #ffffff; font-size: 24px; margin-bottom: 30px; }
|
||||
.metrics-box { display: flex; gap: 15px; margin-bottom: 20px; }
|
||||
.card { background: #1e293b; border-radius: 8px; padding: 20px; flex: 1; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); }
|
||||
.card h3 { margin: 0 0 8px; font-size: 14px; color: #38bdf8; }
|
||||
.card p { font-size: 18px; margin: 0; font-weight: bold; color: #ffffff; }
|
||||
.chart-container { background: #1e293b; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); }
|
||||
.status-ok { color: #4ade80 !important; }
|
||||
.status-down { color: #f87171 !important; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; background: #1e293b; border-radius: 8px; overflow: hidden; }
|
||||
th, td { padding: 12px; text-align: center; font-size: 14px; border-bottom: 1px solid #334155; }
|
||||
th { background: #0f172a; color: #38bdf8; }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>لوحة مراقبة موقع مثال (mithal.space)</h1>
|
||||
|
||||
<div class="metrics-box">
|
||||
<div class="card">
|
||||
<h3>حالة الموقع</h3>
|
||||
<p id="siteStatus" class="status-ok">جاري التحميل...</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>حالة SSL وتاريخ الانتهاء</h3>
|
||||
<p id="sslStatus" style="font-size: 14px;">جاري التحميل...</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>متوسط الـ Latency</h3>
|
||||
<p id="latencyStatus">جاري التحميل...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- الرسم البياني (Line Chart) لزمن الاستجابة -->
|
||||
<div class="chart-container">
|
||||
<h3 style="color: #38bdf8; margin-top: 0; text-align: right;">رسم بياني لزمن الاستجابة (Latency - آخر ساعة)</h3>
|
||||
<canvas id="latencyChart"></canvas>
|
||||
</div>
|
||||
|
||||
<h2 style="font-size: 18px; margin-top: 30px; color: #38bdf8; text-align: right;">سجل آخر الفحصات</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>الوقت</th>
|
||||
<th>حالة الموقع</th>
|
||||
<th>زمن الاستجابة</th>
|
||||
<th>زمن البحث</th>
|
||||
<th>حالة SSL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="logTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// إعداد الرسم البياني باستخدام Chart.js
|
||||
const ctx = document.getElementById('latencyChart').getContext('2d');
|
||||
const latencyChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: 'زمن الاستجابة (ms)',
|
||||
data: [],
|
||||
borderColor: '#38bdf8',
|
||||
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { labels: { color: '#ffffff' } }
|
||||
},
|
||||
scales: {
|
||||
x: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } },
|
||||
y: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchMetrics() {
|
||||
try {
|
||||
const response = await fetch('/api/metrics');
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.length > 0) {
|
||||
const latest = data[data.length - 1];
|
||||
|
||||
// تحديث الكروت العلوية
|
||||
const statusElem = document.getElementById('siteStatus');
|
||||
if (latest.status_code === 200) {
|
||||
statusElem.textContent = "متصل (200 OK)";
|
||||
statusElem.className = "status-ok";
|
||||
} else {
|
||||
statusElem.textContent = `غير متصل (${latest.status_code})`;
|
||||
statusElem.className = "status-down";
|
||||
}
|
||||
|
||||
document.getElementById('sslStatus').textContent = latest.ssl_status;
|
||||
document.getElementById('latencyStatus').textContent = `${latest.latency_ms} ms`;
|
||||
|
||||
// تحديث بيانات الرسم البياني (Line Chart) من السكريبت
|
||||
const timestamps = data.map(row => row.timestamp.split(' ')[1]); // أخذ الوقت فقط
|
||||
const latencies = data.map(row => row.latency_ms);
|
||||
|
||||
latencyChart.data.labels = timestamps;
|
||||
latencyChart.data.datasets[0].data = latencies;
|
||||
latencyChart.update();
|
||||
|
||||
// تحديث الجدول
|
||||
const tableBody = document.getElementById('logTableBody');
|
||||
tableBody.innerHTML = '';
|
||||
const reversedData = [...data].reverse().slice(0, 10);
|
||||
reversedData.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${row.timestamp}</td>
|
||||
<td class="${row.status_code === 200 ? 'status-ok' : 'status-down'}">${row.status_code} OK</td>
|
||||
<td>${row.latency_ms} ms</td>
|
||||
<td>${row.search_response_ms} ms</td>
|
||||
<td>${row.ssl_status}</td>
|
||||
`;
|
||||
tableBody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error fetching metrics");
|
||||
}
|
||||
}
|
||||
|
||||
fetchMetrics();
|
||||
setInterval(fetchMetrics, 5000); // تحديث الجراف والبيانات تلقائياً كل 5 ثوانٍ
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
74
q5-mithal-monitor/metrics.json
Normal file
74
q5-mithal-monitor/metrics.json
Normal file
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"timestamp": "2026-07-27 19:52:03",
|
||||
"status_code": 200,
|
||||
"latency_ms": 1041.82,
|
||||
"dns_ms": 9.59,
|
||||
"search_response_ms": 944.04,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 19:53:06",
|
||||
"status_code": 200,
|
||||
"latency_ms": 961.73,
|
||||
"dns_ms": 1.01,
|
||||
"search_response_ms": 951.24,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 19:54:09",
|
||||
"status_code": 200,
|
||||
"latency_ms": 1117.17,
|
||||
"dns_ms": 21.45,
|
||||
"search_response_ms": 849.41,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 19:55:12",
|
||||
"status_code": 200,
|
||||
"latency_ms": 1785.42,
|
||||
"dns_ms": 0.0,
|
||||
"search_response_ms": 846.86,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 19:56:23",
|
||||
"status_code": 200,
|
||||
"latency_ms": 3183.29,
|
||||
"dns_ms": 1.12,
|
||||
"search_response_ms": 4690.82,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 19:57:25",
|
||||
"status_code": 200,
|
||||
"latency_ms": 1111.06,
|
||||
"dns_ms": 8.02,
|
||||
"search_response_ms": 848.63,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 19:58:28",
|
||||
"status_code": 200,
|
||||
"latency_ms": 1030.14,
|
||||
"dns_ms": 1.01,
|
||||
"search_response_ms": 839.49,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 19:59:31",
|
||||
"status_code": 200,
|
||||
"latency_ms": 985.51,
|
||||
"dns_ms": 18.2,
|
||||
"search_response_ms": 931.7,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-27 20:00:33",
|
||||
"status_code": 200,
|
||||
"latency_ms": 936.12,
|
||||
"dns_ms": 13.48,
|
||||
"search_response_ms": 939.24,
|
||||
"ssl_status": "صحيحة (تنتهي خلال 49 يوم)"
|
||||
}
|
||||
]
|
||||
120
q5-mithal-monitor/monitor.py
Normal file
120
q5-mithal-monitor/monitor.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
import ssl
|
||||
import socket
|
||||
import os
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import urlparse
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import JSONResponse, FileResponse
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
# --- تعريف الـ Lifespan لحل مشكلة الـ startup وتوليد الخلفية ---
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
task = asyncio.create_task(background_monitor())
|
||||
yield
|
||||
task.cancel()
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
TARGET_URL = "https://mithal.space"
|
||||
LOG_FILE = "metrics.json"
|
||||
|
||||
# --- دالة الفحص الخلفية بـ FastAPI ---
|
||||
async def background_monitor():
|
||||
while True:
|
||||
parsed_url = urlparse(TARGET_URL)
|
||||
hostname = parsed_url.netloc
|
||||
|
||||
start_time = time.time()
|
||||
status_code = 0
|
||||
latency = 0
|
||||
ssl_expiry = "N/A"
|
||||
dns_time = 0
|
||||
search_time = 0
|
||||
|
||||
try:
|
||||
dns_start = time.time()
|
||||
socket.gethostbyname(hostname)
|
||||
dns_time = round((time.time() - dns_start) * 1000, 2)
|
||||
except:
|
||||
dns_time = -1
|
||||
|
||||
try:
|
||||
response = requests.get(TARGET_URL, timeout=10)
|
||||
latency = round((time.time() - start_time) * 1000, 2)
|
||||
status_code = response.status_code
|
||||
except:
|
||||
latency = -1
|
||||
status_code = 503
|
||||
|
||||
try:
|
||||
search_start = time.time()
|
||||
requests.get(f"{TARGET_URL}/search?q=test", timeout=5)
|
||||
search_time = round((time.time() - search_start) * 1000, 2)
|
||||
except:
|
||||
search_time = -1
|
||||
|
||||
try:
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((hostname, 443), timeout=5) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z').replace(tzinfo=timezone.utc)
|
||||
days_left = (expiry_date - datetime.now(timezone.utc)).days
|
||||
ssl_expiry = f"صحيحة (تنتهي خلال {days_left} يوم)"
|
||||
except:
|
||||
ssl_expiry = "خطأ في جلب SSL"
|
||||
|
||||
timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
record = {
|
||||
"timestamp": timestamp_str,
|
||||
"status_code": status_code,
|
||||
"latency_ms": latency,
|
||||
"dns_ms": dns_time,
|
||||
"search_response_ms": search_time,
|
||||
"ssl_status": ssl_expiry
|
||||
}
|
||||
|
||||
try:
|
||||
if os.path.exists(LOG_FILE):
|
||||
with open(LOG_FILE, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = []
|
||||
except:
|
||||
data = []
|
||||
|
||||
data.append(record)
|
||||
if len(data) > 20:
|
||||
data = data[-20:]
|
||||
|
||||
with open(LOG_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
print(f"[{timestamp_str}] Background Checked {TARGET_URL} - Status: {status_code} - Latency: {latency}ms")
|
||||
|
||||
# الانتظار لمدة 30 ثانية أو دقيقة بحسب رغبتك
|
||||
await asyncio.sleep(60)
|
||||
|
||||
# --- المسارات (Endpoints) ---
|
||||
@app.get("/")
|
||||
def index():
|
||||
if os.path.exists("dashboard.html"):
|
||||
return FileResponse("dashboard.html")
|
||||
return {"message": "Dashboard file not found"}
|
||||
|
||||
@app.get("/api/metrics")
|
||||
def get_metrics():
|
||||
if os.path.exists(LOG_FILE):
|
||||
try:
|
||||
with open(LOG_FILE, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return JSONResponse(content=data)
|
||||
except:
|
||||
return JSONResponse(content=[])
|
||||
return JSONResponse(content=[])
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم