Restructure repository for final submission
CI/CD - Build & Deploy to Ghaymah Cloud / build-and-test (push) Has been cancelled
CI/CD - Build & Deploy to Ghaymah Cloud / deploy-staging (push) Has been cancelled
CI/CD - Build & Deploy to Ghaymah Cloud / deploy-production (push) Has been cancelled

This commit is contained in:
Ubuntu
2026-07-28 13:22:05 +00:00
parent b43f9bf706
commit 54af9f97ac
27 changed files with 1939 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
.venv
__pycache__
*.pyc
.git
monitor-data.json
README.md
+17
View File
@@ -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"
}
+13
View File
@@ -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"]
+161
View File
@@ -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
+66
View File
@@ -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)
+692
View File
@@ -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
}
]
+187
View File
@@ -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)
+4
View File
@@ -0,0 +1,4 @@
Flask==3.1.1
requests==2.32.4
dnspython==2.7.0
gunicorn==23.0.0
+280
View File
@@ -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>