Standalone app for Q5 deployment
هذا الالتزام موجود في:
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY monitor.py .
|
||||
COPY dashboard.html .
|
||||
COPY start.sh .
|
||||
RUN chmod +x start.sh
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["./start.sh"]
|
||||
102
dashboard.html
Normal file
102
dashboard.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>مراقبة mithal.space</title>
|
||||
<style>
|
||||
body { font-family: Arial; background: #0f172a; color: #e2e8f0; padding: 30px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 15px; margin-bottom: 20px; }
|
||||
.card { background: #1e293b; padding: 20px; border-radius: 10px; }
|
||||
.big { font-size: 28px; font-weight: bold; }
|
||||
.status-up { color: #22c55e; }
|
||||
.status-down { color: #ef4444; }
|
||||
canvas { background: #1e293b; border-radius: 10px; padding: 10px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||||
td, th { padding: 8px; border-bottom: 1px solid #334155; text-align: right; font-size: 13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>لوحة مراقبة mithal.space</h1>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div>نسبة التشغيل (24 ساعة)</div>
|
||||
<div class="big" id="uptime-pct">-</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div>حالة SSL</div>
|
||||
<div class="big" id="ssl-status">-</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div>آخر زمن استجابة</div>
|
||||
<div class="big" id="latest-latency">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div>زمن الاستجابة — آخر ساعة</div>
|
||||
<canvas id="latencyChart" height="80"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top:15px;">
|
||||
<h3>آخر 10 فحوصات</h3>
|
||||
<table id="log-table">
|
||||
<thead><tr><th>الوقت</th><th>الحالة</th><th>الاستجابة</th><th>SSL</th><th>DNS</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
|
||||
<script>
|
||||
let chart;
|
||||
|
||||
async function loadData() {
|
||||
const res = await fetch('mithal_monitor_log.json');
|
||||
const text = await res.text();
|
||||
const lines = text.trim().split('\n').filter(Boolean).map(JSON.parse);
|
||||
|
||||
const last = lines[lines.length - 1];
|
||||
const upCount = lines.filter(l => l.uptime.up).length;
|
||||
const uptimePct = ((upCount / lines.length) * 100).toFixed(1);
|
||||
|
||||
document.getElementById('uptime-pct').innerHTML =
|
||||
`<span class="${uptimePct > 95 ? 'status-up' : 'status-down'}">${uptimePct}%</span>`;
|
||||
document.getElementById('ssl-status').innerHTML = last.ssl.valid
|
||||
? `<span class="status-up">صالحة (${last.ssl.days_until_expiry} يوم)</span>`
|
||||
: `<span class="status-down">غير صالحة</span>`;
|
||||
document.getElementById('latest-latency').innerText = last.uptime.latency_ms + ' ms';
|
||||
|
||||
const recent = lines.slice(-60);
|
||||
const labels = recent.map(l => new Date(l.timestamp).toLocaleTimeString('ar-EG'));
|
||||
const data = recent.map(l => l.uptime.latency_ms);
|
||||
|
||||
if (chart) {
|
||||
chart.data.labels = labels;
|
||||
chart.data.datasets[0].data = data;
|
||||
chart.update();
|
||||
} else {
|
||||
chart = new Chart(document.getElementById('latencyChart'), {
|
||||
type: 'line',
|
||||
data: { labels, datasets: [{ label: 'ms', data, borderColor: '#38bdf8', tension: 0.3 }] },
|
||||
options: { plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } }
|
||||
});
|
||||
}
|
||||
|
||||
const tbody = document.querySelector('#log-table tbody');
|
||||
tbody.innerHTML = '';
|
||||
lines.slice(-10).reverse().forEach(l => {
|
||||
tbody.innerHTML += `<tr>
|
||||
<td>${new Date(l.timestamp).toLocaleTimeString('ar-EG')}</td>
|
||||
<td>${l.uptime.up ? '✅' : '❌'}</td>
|
||||
<td>${l.uptime.latency_ms} ms</td>
|
||||
<td>${l.ssl.valid ? '✅' : '❌'}</td>
|
||||
<td>${l.dns.dns_time_ms} ms</td>
|
||||
</tr>`;
|
||||
});
|
||||
}
|
||||
|
||||
loadData();
|
||||
setInterval(loadData, 60000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
84
monitor.py
Executable file
84
monitor.py
Executable file
@@ -0,0 +1,84 @@
|
||||
import requests
|
||||
import socket
|
||||
import ssl
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import urlparse
|
||||
|
||||
TARGET_URL = "https://mithal.space"
|
||||
SEARCH_URL = "https://mithal.space/search?q=test"
|
||||
LOG_FILE = "mithal_monitor_log.json"
|
||||
CHECK_INTERVAL = 60
|
||||
|
||||
|
||||
def check_latency_and_uptime(url):
|
||||
try:
|
||||
start = time.time()
|
||||
response = requests.get(url, timeout=10)
|
||||
latency_ms = round((time.time() - start) * 1000, 2)
|
||||
return {
|
||||
"up": response.status_code < 500,
|
||||
"status_code": response.status_code,
|
||||
"latency_ms": latency_ms
|
||||
}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"up": False, "status_code": None, "latency_ms": None, "error": str(e)}
|
||||
|
||||
|
||||
def check_ssl(hostname):
|
||||
try:
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((hostname, 443), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
expiry_str = cert['notAfter']
|
||||
expiry_date = datetime.strptime(expiry_str, '%b %d %H:%M:%S %Y %Z')
|
||||
days_left = (expiry_date - datetime.utcnow()).days
|
||||
return {"valid": True, "days_until_expiry": days_left}
|
||||
except Exception as e:
|
||||
return {"valid": False, "error": str(e)}
|
||||
|
||||
|
||||
def check_dns(hostname):
|
||||
try:
|
||||
start = time.time()
|
||||
socket.gethostbyname(hostname)
|
||||
dns_time_ms = round((time.time() - start) * 1000, 2)
|
||||
return {"resolved": True, "dns_time_ms": dns_time_ms}
|
||||
except Exception as e:
|
||||
return {"resolved": False, "error": str(e)}
|
||||
|
||||
|
||||
def check_search_response(url):
|
||||
try:
|
||||
start = time.time()
|
||||
response = requests.get(url, timeout=10)
|
||||
latency_ms = round((time.time() - start) * 1000, 2)
|
||||
return {"success": response.status_code == 200, "latency_ms": latency_ms}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
def run_check():
|
||||
hostname = urlparse(TARGET_URL).hostname
|
||||
|
||||
result = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"uptime": check_latency_and_uptime(TARGET_URL),
|
||||
"ssl": check_ssl(hostname),
|
||||
"dns": check_dns(hostname),
|
||||
"search": check_search_response(SEARCH_URL)
|
||||
}
|
||||
|
||||
with open(LOG_FILE, "a") as f:
|
||||
f.write(json.dumps(result) + "\n")
|
||||
|
||||
print(f"[{result['timestamp']}] Check complete — up: {result['uptime']['up']}")
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
while True:
|
||||
run_check()
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
requests
|
||||
dnspython
|
||||
المرجع في مشكلة جديدة
حظر مستخدم