Complete Task 5: Mithal monitoring dashboard

هذا الالتزام موجود في:
2026-07-28 18:57:31 +03:00
الأصل fbcb4e997c
التزام 3a5d1dece0
15 ملفات معدلة مع 800 إضافات و0 حذوفات

عرض الملف

@@ -0,0 +1,13 @@
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
RUN chmod +x start.sh
EXPOSE 8000
CMD ["./start.sh"]

عرض الملف

عرض الملف

@@ -0,0 +1,120 @@
async function loadData() {
try {
const response = await fetch("data.json?t=" + Date.now());
const data = await response.json();
if (data.length === 0) {
return;
}
const latest = data[data.length - 1];
const upCount = data.filter(item => item.uptime === "UP").length;
const uptimePercentage = ((upCount / data.length) * 100).toFixed(1);
document.getElementById("uptime").innerText =
uptimePercentage + "%";
document.getElementById("latency").innerText =
(latest.latency_ms ?? "-") + " ms";
document.getElementById("dns").innerText =
(latest.dns_ms ?? "-") + " ms";
if (latest.ssl && latest.ssl.valid) {
document.getElementById("ssl").innerHTML = `
<strong style="color:green;">Valid</strong><br>
<small>${latest.ssl.expires}</small>
`;
} else {
document.getElementById("ssl").innerHTML = `
<strong style="color:red;">Invalid</strong>
`;
}
const table = document.getElementById("historyTable");
table.innerHTML = "";
data
.slice()
.reverse()
.forEach(item => {
table.innerHTML += `
<tr>
<td>${item.timestamp}</td>
<td>${item.uptime}</td>
<td>${item.latency_ms ?? "-"}</td>
<td>${item.dns_ms ?? "-"}</td>
<td>${item.ssl && item.ssl.valid ? "Valid" : "Invalid"}</td>
</tr>
`;
});
const labels = data.map(item => item.timestamp);
const latency = data.map(item => item.latency_ms);
if (!window.latencyChart) {
window.latencyChart = new Chart(
document.getElementById("latencyChart"),
{
type: "line",
data: {
labels: labels,
datasets: [{
label: "Latency (ms)",
data: latency,
borderColor: "#0b7dda",
backgroundColor: "rgba(11,125,218,0.15)",
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: true
}
},
scales: {
y: {
beginAtZero: false
}
}
}
}
);
} else {
window.latencyChart.data.labels = labels;
window.latencyChart.data.datasets[0].data = latency;
window.latencyChart.update();
}
}
catch (error) {
console.error(error);
}
}
loadData();
setInterval(loadData, 60000);

عرض الملف

@@ -0,0 +1,62 @@
[
{
"timestamp": "2026-07-28 17:50:54",
"status_code": 200,
"uptime": "UP",
"latency_ms": 994.55,
"dns_ms": 39.1,
"search_response_ms": 1170.81,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:50:58",
"status_code": 200,
"uptime": "UP",
"latency_ms": 986.42,
"dns_ms": 40.91,
"search_response_ms": 1130.27,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:51:01",
"status_code": 200,
"uptime": "UP",
"latency_ms": 980.57,
"dns_ms": 51.6,
"search_response_ms": 1168.75,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:51:04",
"status_code": 200,
"uptime": "UP",
"latency_ms": 969.79,
"dns_ms": 40.75,
"search_response_ms": 1209.84,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:51:07",
"status_code": 200,
"uptime": "UP",
"latency_ms": 976.47,
"dns_ms": 42.13,
"search_response_ms": 1147.52,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
}
]

عرض الملف

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mithal Monitoring Dashboard</title>
<link rel="stylesheet" href="style.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="container">
<h1>Mithal Monitoring Dashboard</h1>
<p class="subtitle">
Website Monitoring Dashboard for mithal.space
</p>
<div class="cards">
<div class="card">
<h2>Uptime</h2>
<p id="uptime">--</p>
</div>
<div class="card">
<h2>Latency</h2>
<p id="latency">--</p>
</div>
<div class="card">
<h2>DNS Time</h2>
<p id="dns">--</p>
</div>
<div class="card">
<h2>SSL</h2>
<p id="ssl">--</p>
</div>
</div>
<div class="chart-card">
<h2>Latency History</h2>
<canvas id="latencyChart"></canvas>
</div>
<div class="table-card">
<h2>Last 10 Checks</h2>
<table>
<thead>
<tr>
<th>Time</th>
<th>Status</th>
<th>Latency (ms)</th>
<th>DNS (ms)</th>
<th>SSL</th>
</tr>
</thead>
<tbody id="historyTable">
</tbody>
</table>
</div>
</div>
<script src="app.js"></script>
</body>
</html>

عرض الملف

@@ -0,0 +1,122 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, Helvetica, sans-serif;
background: #f4f7fb;
color: #333;
}
.container {
width: 95%;
max-width: 1300px;
margin: 40px auto;
}
h1 {
text-align: center;
margin-bottom: 10px;
color: #2c3e50;
}
.subtitle {
text-align: center;
margin-bottom: 35px;
color: gray;
}
.cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 35px;
}
.card {
background: white;
border-radius: 10px;
padding: 25px;
text-align: center;
box-shadow: 0 2px 8px rgba(0,0,0,.08);
}
.card h2 {
margin-bottom: 15px;
color: #555;
font-size: 20px;
}
.card p {
font-size: 30px;
font-weight: bold;
color: #0b7dda;
}
.chart-card,
.table-card {
background: white;
border-radius: 10px;
padding: 25px;
margin-top: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,.08);
}
.chart-card h2,
.table-card h2 {
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
}
thead {
background: #0b7dda;
color: white;
}
th,
td {
padding: 12px;
text-align: center;
border-bottom: 1px solid #ddd;
}
tbody tr:hover {
background: #f5f5f5;
}
canvas {
width: 100% !important;
max-height: 350px;
}
@media (max-width:900px){
.cards{
grid-template-columns:repeat(2,1fr);
}
}
@media (max-width:600px){
.cards{
grid-template-columns:1fr;
}
table{
font-size:12px;
}
}

عرض الملف

@@ -0,0 +1,62 @@
[
{
"timestamp": "2026-07-28 17:50:54",
"status_code": 200,
"uptime": "UP",
"latency_ms": 994.55,
"dns_ms": 39.1,
"search_response_ms": 1170.81,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:50:58",
"status_code": 200,
"uptime": "UP",
"latency_ms": 986.42,
"dns_ms": 40.91,
"search_response_ms": 1130.27,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:51:01",
"status_code": 200,
"uptime": "UP",
"latency_ms": 980.57,
"dns_ms": 51.6,
"search_response_ms": 1168.75,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:51:04",
"status_code": 200,
"uptime": "UP",
"latency_ms": 969.79,
"dns_ms": 40.75,
"search_response_ms": 1209.84,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
},
{
"timestamp": "2026-07-28 17:51:07",
"status_code": 200,
"uptime": "UP",
"latency_ms": 976.47,
"dns_ms": 42.13,
"search_response_ms": 1147.52,
"ssl": {
"valid": true,
"expires": "Sep 15 13:10:47 2026 GMT"
}
}
]

عرض الملف

@@ -0,0 +1,135 @@
import json
import os
import socket
import ssl
import time
from datetime import datetime
import dns.resolver
import requests
URL = "https://mithal.space"
DATA_FILE = "data.json"
def get_dns_time():
try:
start = time.time()
dns.resolver.resolve("mithal.space", "A")
end = time.time()
return round((end - start) * 1000, 2)
except Exception:
return None
def get_ssl_info():
try:
hostname = "mithal.space"
context = ssl.create_default_context()
with context.wrap_socket(
socket.socket(),
server_hostname=hostname
) as sock:
sock.settimeout(10)
sock.connect((hostname, 443))
cert = sock.getpeercert()
return {
"valid": True,
"expires": cert["notAfter"]
}
except Exception:
return {
"valid": False,
"expires": None
}
def get_search_response():
try:
start = time.time()
requests.get(URL, timeout=10)
end = time.time()
return round((end - start) * 1000, 2)
except Exception:
return None
def check_website():
try:
response = requests.get(URL, timeout=10)
result = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status_code": response.status_code,
"uptime": "UP" if response.status_code == 200 else "DOWN",
"latency_ms": round(response.elapsed.total_seconds() * 1000, 2),
"dns_ms": get_dns_time(),
"search_response_ms": get_search_response(),
"ssl": get_ssl_info()
}
except Exception as e:
result = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"status_code": None,
"uptime": "DOWN",
"latency_ms": None,
"dns_ms": None,
"search_response_ms": None,
"ssl": {
"valid": False,
"expires": None
},
"error": str(e)
}
return result
def load_data():
if not os.path.exists(DATA_FILE):
return []
with open(DATA_FILE, "r") as file:
try:
return json.load(file)
except json.JSONDecodeError:
return []
def save_data(data):
with open(DATA_FILE, "w") as file:
json.dump(data, file, indent=4)
def main():
data = load_data()
result = check_website()
data.append(result)
data = data[-10:]
save_data(data)
print(result)
if __name__ == "__main__":
main()

عرض الملف

@@ -0,0 +1,2 @@
requests
dnspython

عرض الملف

@@ -0,0 +1,13 @@
#!/bin/sh
while true
do
python monitor.py
cp data.json dashboard/data.json
sleep 60
done &
cd dashboard
python -m http.server 8000