Complete Task 5: Mithal monitoring dashboard
هذا الالتزام موجود في:
ثنائية
q4-scalability/architecture-diagram.png
Executable file
ثنائية
q4-scalability/architecture-diagram.png
Executable file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 1.2 MiB |
49
q4-scalability/architecture.md
Normal file
49
q4-scalability/architecture.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Scalable Architecture Design
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The application is designed to handle up to **15,000 requests per second (req/s)** on Ghaymah using multiple application containers behind a Load Balancer.
|
||||||
|
|
||||||
|
The architecture distributes incoming traffic evenly across multiple container instances to ensure high availability, scalability, and fault tolerance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Components
|
||||||
|
|
||||||
|
- Internet Users
|
||||||
|
- Ghaymah Load Balancer
|
||||||
|
- Multiple Application Containers
|
||||||
|
- Ghaymah Block Storage
|
||||||
|
- Database
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Traffic Flow
|
||||||
|
|
||||||
|
1. Users send requests to the application.
|
||||||
|
2. The Load Balancer receives all incoming traffic.
|
||||||
|
3. Requests are distributed evenly across the application containers.
|
||||||
|
4. Each container processes the request.
|
||||||
|
5. Stateful data is stored in the database using Ghaymah Block Storage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## High Availability
|
||||||
|
|
||||||
|
If one container becomes unavailable, the Load Balancer automatically routes traffic to healthy containers without affecting users.
|
||||||
|
|
||||||
|
Internet Users
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Ghaymah Load Balancer
|
||||||
|
│
|
||||||
|
┌───────────────┼───────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
App Container App Container App Container
|
||||||
|
│ │ │
|
||||||
|
└───────────────┼───────────────┘
|
||||||
|
│
|
||||||
|
Database
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Ghaymah Block Storage
|
||||||
49
q4-scalability/block-storage.md
Normal file
49
q4-scalability/block-storage.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Ghaymah Block Storage for Stateful Workloads
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Stateful applications store persistent data that must remain available even if a container is restarted, replaced, or moved to another node.
|
||||||
|
|
||||||
|
Ghaymah Block Storage provides persistent storage that can be attached to stateful workloads such as databases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Stateful Workloads
|
||||||
|
|
||||||
|
- MySQL
|
||||||
|
- PostgreSQL
|
||||||
|
- MongoDB
|
||||||
|
- Redis (Persistent Mode)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. The database container stores its data on a Ghaymah Block Storage volume.
|
||||||
|
2. If the container fails, a new container can be started.
|
||||||
|
3. The same Block Storage volume is reattached.
|
||||||
|
4. The application continues using the existing data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- Persistent data storage
|
||||||
|
- High durability
|
||||||
|
- Faster recovery after failures
|
||||||
|
- Data remains available after container restarts
|
||||||
|
- Suitable for production database workloads
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
Application Containers
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Database Container
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Ghaymah Block Storage
|
||||||
|
```
|
||||||
38
q4-scalability/cold-start-strategy.md
Normal file
38
q4-scalability/cold-start-strategy.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Cold Start Strategy
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A cold start occurs when a new application container is created to handle increased traffic. During startup, the container needs time to initialize before it can receive requests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Strategy
|
||||||
|
|
||||||
|
### 1. Keep Warm Containers
|
||||||
|
|
||||||
|
Maintain a small number of standby containers that are already running and ready to receive traffic immediately.
|
||||||
|
|
||||||
|
### 2. Auto Scaling
|
||||||
|
|
||||||
|
Automatically create additional containers when CPU usage, memory usage, or request rate exceeds predefined thresholds.
|
||||||
|
|
||||||
|
### 3. Lightweight Container Images
|
||||||
|
|
||||||
|
Use small Docker images to reduce image download and startup time.
|
||||||
|
|
||||||
|
### 4. Health Checks
|
||||||
|
|
||||||
|
Only send traffic to containers after they successfully pass health checks.
|
||||||
|
|
||||||
|
### 5. Container Image Caching
|
||||||
|
|
||||||
|
Cache frequently used container images on the compute nodes to avoid downloading them repeatedly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- Faster application startup
|
||||||
|
- Lower request latency
|
||||||
|
- Better user experience during traffic spikes
|
||||||
|
- Improved application availability
|
||||||
41
q4-scalability/container-calculation.md
Normal file
41
q4-scalability/container-calculation.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Container Capacity Calculation
|
||||||
|
|
||||||
|
## Given
|
||||||
|
|
||||||
|
- Expected traffic: **15,000 requests/second**
|
||||||
|
- Capacity per container: **500 requests/second**
|
||||||
|
- Safety margin: **30%**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Calculate Minimum Containers
|
||||||
|
|
||||||
|
Minimum Containers = Total Requests ÷ Capacity per Container
|
||||||
|
|
||||||
|
```
|
||||||
|
15000 ÷ 500 = 30 Containers
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Add Safety Margin
|
||||||
|
|
||||||
|
```
|
||||||
|
30 × 30% = 9 Containers
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Total Required Containers
|
||||||
|
|
||||||
|
```
|
||||||
|
30 + 9 = 39 Containers
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Final Result
|
||||||
|
|
||||||
|
To safely handle **15,000 requests per second**, the application should run **39 application containers**.
|
||||||
|
|
||||||
|
The additional containers provide extra capacity during traffic spikes and improve overall reliability.
|
||||||
13
q5-mithal-monitor/Dockerfile
Normal file
13
q5-mithal-monitor/Dockerfile
Normal file
@@ -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
q5-mithal-monitor/README.md
Normal file
0
q5-mithal-monitor/README.md
Normal file
120
q5-mithal-monitor/dashboard/app.js
Normal file
120
q5-mithal-monitor/dashboard/app.js
Normal file
@@ -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);
|
||||||
62
q5-mithal-monitor/dashboard/data.json
Normal file
62
q5-mithal-monitor/dashboard/data.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
94
q5-mithal-monitor/dashboard/index.html
Normal file
94
q5-mithal-monitor/dashboard/index.html
Normal file
@@ -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>
|
||||||
122
q5-mithal-monitor/dashboard/style.css
Normal file
122
q5-mithal-monitor/dashboard/style.css
Normal file
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
62
q5-mithal-monitor/data.json
Normal file
62
q5-mithal-monitor/data.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
135
q5-mithal-monitor/monitor.py
Normal file
135
q5-mithal-monitor/monitor.py
Normal file
@@ -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()
|
||||||
2
q5-mithal-monitor/requirements.txt
Normal file
2
q5-mithal-monitor/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
requests
|
||||||
|
dnspython
|
||||||
13
q5-mithal-monitor/start.sh
Normal file
13
q5-mithal-monitor/start.sh
Normal file
@@ -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
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم