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
|
||||
المرجع في مشكلة جديدة
حظر مستخدم