Restructure repository for final submission
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
.venv
|
||||
__pycache__
|
||||
*.pyc
|
||||
.git
|
||||
monitor-data.json
|
||||
README.md
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "bbf0b1b5-2214-479e-b5e7-00be81bd0e7d",
|
||||
"name": "mithal-monitor",
|
||||
"projectId": "6d1bb957-8a2c-4a88-86f3-6a937b7971f0",
|
||||
"ports": [
|
||||
{
|
||||
"expose": true,
|
||||
"number": 5000
|
||||
}
|
||||
],
|
||||
"publicAccess": {
|
||||
"enabled": true,
|
||||
"domain": "auto"
|
||||
},
|
||||
"resourceTier": "t1",
|
||||
"dockerFileName": "Dockerfile"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
|
||||
@@ -0,0 +1,161 @@
|
||||
# Mithal Monitor
|
||||
|
||||
A Flask-based monitoring application that periodically collects website metrics and exposes them through REST APIs. The application is containerized with Docker and deployed on Ghaymah.
|
||||
|
||||
## Features
|
||||
|
||||
- Monitor website availability
|
||||
- Measure DNS lookup time
|
||||
- Measure HTTP latency
|
||||
- Measure search latency
|
||||
- Check SSL certificate expiration
|
||||
- Store collected metrics
|
||||
- REST API for health checks and metrics
|
||||
- Dockerized deployment
|
||||
- Deployed on Ghaymah
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Python
|
||||
- Flask
|
||||
- SQLite
|
||||
- Docker
|
||||
- Gunicorn
|
||||
- Ghaymah
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Health Check
|
||||
|
||||
```http
|
||||
GET /health
|
||||
```
|
||||
|
||||
Example Response
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Metrics
|
||||
|
||||
```http
|
||||
GET /api/metrics
|
||||
```
|
||||
|
||||
Example Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"timestamp": "2026-07-28 12:11:50",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 34.03,
|
||||
"dns_ms": 2.38,
|
||||
"search_latency_ms": 108.42,
|
||||
"ssl_days_left": 49,
|
||||
"ssl_expiry": "2026-09-15"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running Locally
|
||||
|
||||
Clone the repository
|
||||
|
||||
```bash
|
||||
git clone <repo-url>
|
||||
cd Q5-Mithal-Monitor
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Run the application
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
The application will be available on
|
||||
|
||||
```
|
||||
http://localhost:5000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
Build the image
|
||||
|
||||
```bash
|
||||
docker build -t mithal-monitor .
|
||||
```
|
||||
|
||||
Run the container
|
||||
|
||||
```bash
|
||||
docker run -p 80:80 mithal-monitor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
The application is deployed on Ghaymah.
|
||||
|
||||
Deployment URL:
|
||||
|
||||
```
|
||||
https://mithal-monitor-74639f9fbe38.hosted.ghaymah.systems
|
||||
```
|
||||
|
||||
Health Endpoint
|
||||
|
||||
```
|
||||
https://mithal-monitor-74639f9fbe38.hosted.ghaymah.systems/health
|
||||
```
|
||||
|
||||
Metrics Endpoint
|
||||
|
||||
```
|
||||
https://mithal-monitor-74639f9fbe38.hosted.ghaymah.systems/api/metrics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
Q5-Mithal-Monitor/
|
||||
│
|
||||
├── app.py
|
||||
├── requirements.txt
|
||||
├── Dockerfile
|
||||
├── monitor.db
|
||||
├── templates/
|
||||
├── static/
|
||||
├── .ghaymah.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
Mohammed Hamdy
|
||||
@@ -0,0 +1,66 @@
|
||||
from flask import Flask, jsonify, render_template
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import monitor # our monitor.py — reused directly, not run as a separate process
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
DATA_FILE = "monitor-data.json"
|
||||
|
||||
|
||||
def background_monitor_loop():
|
||||
"""Runs monitor.monitor() every CHECK_INTERVAL seconds inside this
|
||||
same process/container, since the Dockerfile only starts one
|
||||
process (gunicorn running app.py). This replaces running
|
||||
monitor.py as a separate process, which never actually happened
|
||||
after deployment (only app.py ran, monitor.py was never invoked)."""
|
||||
print("Starting background monitor thread...")
|
||||
while True:
|
||||
try:
|
||||
monitor.monitor()
|
||||
except Exception as e:
|
||||
print("Monitor thread error:", e)
|
||||
time.sleep(monitor.CHECK_INTERVAL)
|
||||
|
||||
|
||||
# Start the background thread once, when this module is imported by
|
||||
# gunicorn. Safe because the Dockerfile runs a single gunicorn worker
|
||||
# (no --workers flag => defaults to 1), so this thread won't be
|
||||
# duplicated across multiple worker processes.
|
||||
monitor_thread = threading.Thread(target=background_monitor_loop, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def home():
|
||||
return render_template("dashboard.html")
|
||||
|
||||
|
||||
@app.route("/dashboard")
|
||||
def dashboard():
|
||||
return render_template("dashboard.html")
|
||||
|
||||
|
||||
@app.route("/api/metrics")
|
||||
def metrics():
|
||||
|
||||
if not os.path.exists(DATA_FILE):
|
||||
return jsonify([])
|
||||
|
||||
with open(DATA_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return {
|
||||
"status": "healthy"
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
@@ -0,0 +1,692 @@
|
||||
[
|
||||
{
|
||||
"timestamp": "2026-07-28 06:15:26",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 138.66,
|
||||
"dns_ms": 1.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 83.64
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:16:27",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 77.9,
|
||||
"dns_ms": 1.0,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.89
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:17:27",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 76.17,
|
||||
"dns_ms": 0.99,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 90.49
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:18:27",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 83.58,
|
||||
"dns_ms": 1.11,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.83
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:19:27",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 78.07,
|
||||
"dns_ms": 0.82,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 83.3
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:20:27",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 96.93,
|
||||
"dns_ms": 1.13,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 78.55
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:21:28",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 78.4,
|
||||
"dns_ms": 1.37,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.19
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:22:28",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 88.03,
|
||||
"dns_ms": 0.89,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 79.74
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:23:28",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 77.57,
|
||||
"dns_ms": 1.22,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.56
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:24:28",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 75.44,
|
||||
"dns_ms": 0.98,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 83.31
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:25:28",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.72,
|
||||
"dns_ms": 0.9,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.22
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:26:29",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 81.2,
|
||||
"dns_ms": 0.87,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 77.07
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:27:29",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 76.89,
|
||||
"dns_ms": 1.5,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.69
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:28:29",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 78.55,
|
||||
"dns_ms": 0.84,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 91.66
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:29:29",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 82.33,
|
||||
"dns_ms": 1.07,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.03
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:30:30",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 91.92,
|
||||
"dns_ms": 0.87,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.92
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:31:30",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.75,
|
||||
"dns_ms": 0.9,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.38
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:32:30",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 79.62,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.62
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:33:30",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 75.97,
|
||||
"dns_ms": 0.82,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 82.48
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:34:30",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 79.56,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 73.19
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:35:31",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 98.91,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 82.02
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:36:31",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 74.78,
|
||||
"dns_ms": 0.92,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 72.29
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:37:31",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 77.6,
|
||||
"dns_ms": 0.89,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.45
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:38:31",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 78.87,
|
||||
"dns_ms": 1.08,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.15
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:39:31",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.61,
|
||||
"dns_ms": 1.34,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 72.64
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:40:32",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.76,
|
||||
"dns_ms": 0.85,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 78.7
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:41:32",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 85.68,
|
||||
"dns_ms": 0.83,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 80.57
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:42:39",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 6717.43,
|
||||
"dns_ms": 0.87,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 83.14
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:43:39",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 76.05,
|
||||
"dns_ms": 0.82,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.75
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:44:39",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 78.1,
|
||||
"dns_ms": 0.8,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 85.88
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:45:39",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 112.41,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 81.98
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:46:40",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 77.23,
|
||||
"dns_ms": 0.92,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 73.45
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:47:40",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 79.96,
|
||||
"dns_ms": 0.85,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 73.76
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:48:40",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 82.81,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 73.3
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:49:40",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 83.33,
|
||||
"dns_ms": 0.87,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 72.03
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:50:40",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 81.64,
|
||||
"dns_ms": 0.85,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 73.87
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:51:41",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 87.24,
|
||||
"dns_ms": 0.83,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 79.31
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:52:41",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.97,
|
||||
"dns_ms": 1.19,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 77.43
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:53:41",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 81.17,
|
||||
"dns_ms": 0.98,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 73.72
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:54:41",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 82.47,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.56
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:55:41",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.53,
|
||||
"dns_ms": 0.98,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 80.2
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:56:42",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 83.64,
|
||||
"dns_ms": 0.82,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 71.71
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:57:42",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 81.55,
|
||||
"dns_ms": 0.84,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 72.56
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:58:42",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 82.6,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 78.6
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 06:59:42",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 79.31,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.71
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:00:43",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 99.38,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 73.06
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:01:43",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 78.82,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 79.59
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:02:43",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 97.32,
|
||||
"dns_ms": 1.02,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 85.14
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:03:43",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 97.17,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.99
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:04:43",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 77.36,
|
||||
"dns_ms": 0.94,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.38
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:05:44",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 126.34,
|
||||
"dns_ms": 0.94,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 78.33
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:06:44",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 78.6,
|
||||
"dns_ms": 0.83,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 77.94
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:07:44",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.89,
|
||||
"dns_ms": 0.84,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.3
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:08:44",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 77.8,
|
||||
"dns_ms": 1.0,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 79.14
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:09:45",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 83.49,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.71
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:10:45",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 72.7,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 80.3
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:11:45",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 79.11,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 79.6
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:12:45",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.55,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.45
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:13:45",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 80.62,
|
||||
"dns_ms": 0.85,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 76.94
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:14:46",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 82.55,
|
||||
"dns_ms": 0.85,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.5
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:15:47",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 986.76,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 84.61
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:16:47",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 103.86,
|
||||
"dns_ms": 0.98,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 100.18
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:17:48",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 910.26,
|
||||
"dns_ms": 0.88,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 81.43
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:18:49",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 910.72,
|
||||
"dns_ms": 0.89,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 83.03
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:19:49",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 76.95,
|
||||
"dns_ms": 0.86,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.19
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:20:50",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 93.67,
|
||||
"dns_ms": 1.39,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.06
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:21:50",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 83.26,
|
||||
"dns_ms": 0.87,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 74.3
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:22:50",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 83.7,
|
||||
"dns_ms": 0.84,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 77.12
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-07-28 07:23:50",
|
||||
"uptime": true,
|
||||
"status_code": 200,
|
||||
"latency_ms": 76.43,
|
||||
"dns_ms": 0.87,
|
||||
"ssl_expiry": "2026-09-15",
|
||||
"ssl_days_left": 49,
|
||||
"search_latency_ms": 75.44
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,187 @@
|
||||
import requests
|
||||
import socket
|
||||
import ssl
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import dns.resolver
|
||||
|
||||
from datetime import datetime, UTC
|
||||
|
||||
# ===========================================
|
||||
# Configuration
|
||||
# ===========================================
|
||||
|
||||
BASE_URL = "https://mithal.space"
|
||||
SEARCH_URL = "https://mithal.space/search?q=cloud"
|
||||
|
||||
OUTPUT_FILE = "monitor-data.json"
|
||||
|
||||
CHECK_INTERVAL = 60
|
||||
|
||||
# ===========================================
|
||||
# HTTP Latency + Uptime
|
||||
# ===========================================
|
||||
|
||||
def check_http():
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
response = requests.get(BASE_URL, timeout=10)
|
||||
|
||||
latency = (time.perf_counter() - start) * 1000
|
||||
|
||||
return response.status_code, round(latency, 2)
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Search Response
|
||||
# ===========================================
|
||||
|
||||
def check_search():
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
response = requests.get(SEARCH_URL, timeout=10)
|
||||
|
||||
latency = (time.perf_counter() - start) * 1000
|
||||
|
||||
return round(latency, 2)
|
||||
|
||||
|
||||
# ===========================================
|
||||
# DNS Lookup Time
|
||||
# ===========================================
|
||||
|
||||
def check_dns():
|
||||
|
||||
resolver = dns.resolver.Resolver()
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
resolver.resolve("mithal.space")
|
||||
|
||||
latency = (time.perf_counter() - start) * 1000
|
||||
|
||||
return round(latency, 2)
|
||||
|
||||
|
||||
# ===========================================
|
||||
# SSL Certificate
|
||||
# ===========================================
|
||||
|
||||
def check_ssl():
|
||||
|
||||
hostname = "mithal.space"
|
||||
|
||||
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 = datetime.strptime(
|
||||
cert["notAfter"],
|
||||
"%b %d %H:%M:%S %Y %Z"
|
||||
).replace(tzinfo=UTC)
|
||||
|
||||
days_left = (expiry - datetime.now(UTC)).days
|
||||
|
||||
return expiry.strftime("%Y-%m-%d"), days_left
|
||||
|
||||
|
||||
# ===========================================
|
||||
# JSON
|
||||
# ===========================================
|
||||
|
||||
def load_history():
|
||||
|
||||
if not os.path.exists(OUTPUT_FILE):
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(OUTPUT_FILE, "r") as f:
|
||||
|
||||
content = f.read().strip()
|
||||
|
||||
if not content:
|
||||
return []
|
||||
|
||||
return json.loads(content)
|
||||
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def save_history(data):
|
||||
|
||||
with open(OUTPUT_FILE, "w") as f:
|
||||
|
||||
json.dump(data, f, indent=4)
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Monitoring
|
||||
# ===========================================
|
||||
|
||||
def monitor():
|
||||
|
||||
status_code, latency = check_http()
|
||||
|
||||
ssl_expiry, ssl_days = check_ssl()
|
||||
|
||||
entry = {
|
||||
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
|
||||
"uptime": status_code == 200,
|
||||
|
||||
"status_code": status_code,
|
||||
|
||||
"latency_ms": latency,
|
||||
|
||||
"dns_ms": check_dns(),
|
||||
|
||||
"ssl_expiry": ssl_expiry,
|
||||
|
||||
"ssl_days_left": ssl_days,
|
||||
|
||||
"search_latency_ms": check_search()
|
||||
|
||||
}
|
||||
|
||||
history = load_history()
|
||||
|
||||
history.append(entry)
|
||||
|
||||
# آخر 24 ساعة (كل دقيقة)
|
||||
history = history[-1440:]
|
||||
|
||||
save_history(history)
|
||||
|
||||
print("=" * 60)
|
||||
print(json.dumps(entry, indent=4))
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Main Loop
|
||||
# ===========================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
print("Starting Mithal Monitoring...")
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
|
||||
monitor()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
print("ERROR:", e)
|
||||
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
@@ -0,0 +1,4 @@
|
||||
Flask==3.1.1
|
||||
requests==2.32.4
|
||||
dnspython==2.7.0
|
||||
gunicorn==23.0.0
|
||||
@@ -0,0 +1,280 @@
|
||||
|
||||
|
||||
<!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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
|
||||
*{
|
||||
margin:0;
|
||||
padding:0;
|
||||
box-sizing:border-box;
|
||||
font-family:Arial,Helvetica,sans-serif;
|
||||
}
|
||||
|
||||
body{
|
||||
background:#0f172a;
|
||||
color:white;
|
||||
padding:30px;
|
||||
}
|
||||
|
||||
.container{
|
||||
max-width:1400px;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
h1{
|
||||
text-align:center;
|
||||
margin-bottom:30px;
|
||||
}
|
||||
|
||||
.grid{
|
||||
display:grid;
|
||||
grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
|
||||
gap:20px;
|
||||
margin-bottom:25px;
|
||||
}
|
||||
|
||||
.card{
|
||||
background:#1e293b;
|
||||
border-radius:12px;
|
||||
padding:20px;
|
||||
box-shadow:0 0 10px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.card h3{
|
||||
color:#94a3b8;
|
||||
margin-bottom:15px;
|
||||
}
|
||||
|
||||
.value{
|
||||
font-size:32px;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.green{
|
||||
color:#22c55e;
|
||||
}
|
||||
|
||||
.red{
|
||||
color:#ef4444;
|
||||
}
|
||||
|
||||
.orange{
|
||||
color:#f59e0b;
|
||||
}
|
||||
|
||||
.chart{
|
||||
background:#1e293b;
|
||||
padding:20px;
|
||||
border-radius:12px;
|
||||
margin-bottom:25px;
|
||||
}
|
||||
|
||||
.table-card{
|
||||
background:#1e293b;
|
||||
border-radius:12px;
|
||||
padding:20px;
|
||||
}
|
||||
|
||||
table{
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
}
|
||||
|
||||
th{
|
||||
background:#334155;
|
||||
padding:12px;
|
||||
}
|
||||
|
||||
td{
|
||||
text-align:center;
|
||||
padding:10px;
|
||||
border-bottom:1px solid #334155;
|
||||
}
|
||||
|
||||
footer{
|
||||
text-align:center;
|
||||
margin-top:25px;
|
||||
color:#94a3b8;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>🚀 Mithal.space Monitoring Dashboard</h1>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<div class="card">
|
||||
<h3>Status</h3>
|
||||
<div id="status" class="value green">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>HTTP Latency</h3>
|
||||
<div id="latency" class="value">--</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>DNS Lookup</h3>
|
||||
<div id="dns" class="value">--</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Search Response</h3>
|
||||
<div id="search" class="value">--</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>SSL Days Left</h3>
|
||||
<div id="ssl" class="value">--</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>24h Uptime</h3>
|
||||
<div id="uptime" class="value">--</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="chart">
|
||||
<canvas id="latencyChart"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<h2 style="margin-bottom:20px;">Last 10 Checks</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Status</th>
|
||||
<th>Latency</th>
|
||||
<th>DNS</th>
|
||||
<th>Search</th>
|
||||
<th>SSL Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="history"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer>Updates every 60 seconds</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
let chart;
|
||||
|
||||
async function loadData(){
|
||||
|
||||
const response = await fetch("/api/metrics");
|
||||
const data = await response.json();
|
||||
|
||||
if(data.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
const latest = data[data.length - 1];
|
||||
|
||||
document.getElementById("status").innerHTML =
|
||||
latest.uptime
|
||||
? '<span class="green">● ONLINE</span>'
|
||||
: '<span class="red">● OFFLINE</span>';
|
||||
|
||||
document.getElementById("latency").innerHTML = latest.latency_ms + " ms";
|
||||
document.getElementById("dns").innerHTML = latest.dns_ms + " ms";
|
||||
document.getElementById("search").innerHTML = latest.search_latency_ms + " ms";
|
||||
document.getElementById("ssl").innerHTML = latest.ssl_days_left + " days";
|
||||
|
||||
const uptimePercent = (
|
||||
data.filter(x => x.uptime).length / data.length * 100
|
||||
).toFixed(2);
|
||||
|
||||
document.getElementById("uptime").innerHTML = uptimePercent + "%";
|
||||
|
||||
const labels = data.slice(-60).map(x => x.timestamp.split(" ")[1]);
|
||||
const latency = data.slice(-60).map(x => x.latency_ms);
|
||||
|
||||
if(chart){
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
chart = new Chart(
|
||||
document.getElementById("latencyChart"),
|
||||
{
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: "Latency (ms)",
|
||||
data: latency,
|
||||
borderWidth: 3,
|
||||
fill: false,
|
||||
tension: .3,
|
||||
borderColor: "#38bdf8"
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: "white"
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const tbody = document.getElementById("history");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
data.slice(-10).reverse().forEach(row => {
|
||||
tbody.innerHTML += `
|
||||
<tr>
|
||||
<td>${row.timestamp}</td>
|
||||
<td>
|
||||
${row.uptime
|
||||
? '<span class="green">Online</span>'
|
||||
: '<span class="red">Offline</span>'}
|
||||
</td>
|
||||
<td>${row.latency_ms} ms</td>
|
||||
<td>${row.dns_ms} ms</td>
|
||||
<td>${row.search_latency_ms} ms</td>
|
||||
<td>${row.ssl_days_left} days</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
loadData();
|
||||
setInterval(loadData, 60000);
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user