Restructure repository for final submission
هذا الالتزام موجود في:
72
common-mortakaz/integration-1.md
Normal file
72
common-mortakaz/integration-1.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Integration Proposal - Product 1: Green Framework
|
||||||
|
|
||||||
|
## 1. Selected Product Description
|
||||||
|
|
||||||
|
- **Product Name:** Green Framework
|
||||||
|
- **Overview:** A modern PHP framework designed for building web applications and REST APIs efficiently. It helps developers create scalable backend applications with a clean and organized structure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. How it integrates with `ghaymah.systems`
|
||||||
|
|
||||||
|
### Proposed Integration
|
||||||
|
|
||||||
|
1. Developers can deploy Green Framework applications on Ghaymah cloud infrastructure.
|
||||||
|
2. Source code can be connected to a CI/CD pipeline to automate building and deployment.
|
||||||
|
3. Docker containers can be used to package the application for consistent deployments.
|
||||||
|
4. Monitoring and logging tools can be connected to monitor application health and performance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Added Value for the End User
|
||||||
|
|
||||||
|
- Faster application deployment.
|
||||||
|
- Easier application maintenance.
|
||||||
|
- More reliable hosting environment.
|
||||||
|
- Better visibility into application health and availability.
|
||||||
|
- Simplified deployment workflow for development teams.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Architecture Sketch
|
||||||
|
|
||||||
|
```text
|
||||||
|
Developer
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Git Repository
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
CI/CD Pipeline
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Docker Container
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Ghaymah Infrastructure
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Green Framework Application
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Monitoring & Logs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Potential Technical or Commercial Challenges
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
- Managing environment variables securely.
|
||||||
|
- Handling database migrations during deployments.
|
||||||
|
- Monitoring application performance across environments.
|
||||||
|
|
||||||
|
### Commercial
|
||||||
|
- Encouraging developers to adopt a new hosting platform.
|
||||||
|
- Providing clear migration guides for existing applications.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Which product is more viable?
|
||||||
|
|
||||||
|
**Green Framework** is the stronger integration candidate because every application built with the framework eventually needs a reliable deployment environment. Integrating it with Ghaymah would provide developers with a complete deployment workflow while supporting modern DevOps practices.
|
||||||
67
common-mortakaz/integration-2.md
Normal file
67
common-mortakaz/integration-2.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Integration Proposal - Product 2: Jadel (جَدَل)
|
||||||
|
|
||||||
|
## 1. Selected Product Description
|
||||||
|
|
||||||
|
- **Product Name:** Jadel
|
||||||
|
- **Overview:** A SaaS platform for managing fitness centers and gyms, including memberships, subscriptions, attendance, and daily business operations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. How it integrates with `ghaymah.systems`
|
||||||
|
|
||||||
|
### Proposed Integration
|
||||||
|
|
||||||
|
1. Host the Jadel platform on Ghaymah infrastructure to improve service availability.
|
||||||
|
2. Run the application and its database in a secure cloud environment.
|
||||||
|
3. Connect monitoring tools to observe application health and detect failures.
|
||||||
|
4. Schedule regular backups to reduce the risk of data loss.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Added Value for the End User
|
||||||
|
|
||||||
|
- Higher application availability.
|
||||||
|
- Improved system reliability.
|
||||||
|
- Better monitoring for administrators.
|
||||||
|
- Easier infrastructure management.
|
||||||
|
- Ability to support business growth without major infrastructure changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Architecture Sketch
|
||||||
|
|
||||||
|
```text
|
||||||
|
Gym Staff
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Jadel Platform
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Ghaymah Infrastructure
|
||||||
|
│
|
||||||
|
┌───┴──────────┐
|
||||||
|
▼ ▼
|
||||||
|
Application Database
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Monitoring & Backups
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Potential Technical or Commercial Challenges
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
- Protecting customer information.
|
||||||
|
- Database migration and backup management.
|
||||||
|
- Maintaining service availability during updates.
|
||||||
|
|
||||||
|
### Commercial
|
||||||
|
- Convincing businesses to migrate from existing systems.
|
||||||
|
- Training users on the new platform.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Which product is more viable?
|
||||||
|
|
||||||
|
Although Jadel would benefit from cloud hosting, its adoption depends mainly on business demand within the fitness industry. Compared to Green Framework, it targets a more specific market, making its integration slightly less flexible.
|
||||||
4
common-qabilah/qabilah-profile.txt
Normal file
4
common-qabilah/qabilah-profile.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Name: Mohammed Hamdy Mahmoud
|
||||||
|
|
||||||
|
Qabilah Profile:
|
||||||
|
https://qabilah.com/profile/mohammedhamdy102003/posts
|
||||||
179
q1-deploy-monitor/README.md
Normal file
179
q1-deploy-monitor/README.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Q1 - Deploy and Monitor an API on Ghaymah Cloud
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This project demonstrates deploying a Dockerized Python API to **Ghaymah Cloud** and implementing a simple monitoring solution.
|
||||||
|
|
||||||
|
The application exposes a `/health` endpoint that is continuously monitored using a Python script. A lightweight HTML dashboard displays the application's health status, response time, uptime, and request statistics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Q1-Deploy-and-Monitoring/
|
||||||
|
│
|
||||||
|
├── screenshots/
|
||||||
|
│ ├── dashboard-local.png
|
||||||
|
│ └── dashboard-ghaymah.png
|
||||||
|
│
|
||||||
|
├── Dockerfile
|
||||||
|
├── app.py
|
||||||
|
├── health-check.py
|
||||||
|
├── dashboard.html
|
||||||
|
├── monitor-log.csv
|
||||||
|
├── requirements.txt
|
||||||
|
├── install-docker.sh
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task Requirements
|
||||||
|
|
||||||
|
This implementation satisfies all requirements of Question 1.
|
||||||
|
|
||||||
|
| Requirement | Status |
|
||||||
|
|------------|--------|
|
||||||
|
| Dockerize the API | ✅ |
|
||||||
|
| Deploy to Ghaymah Cloud | ✅ |
|
||||||
|
| Implement `/health` endpoint | ✅ |
|
||||||
|
| Monitoring script (every 30 seconds) | ✅ |
|
||||||
|
| Monitoring Dashboard | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Technologies
|
||||||
|
|
||||||
|
- Python
|
||||||
|
- Flask
|
||||||
|
- Docker
|
||||||
|
- HTML
|
||||||
|
- CSS
|
||||||
|
- JavaScript
|
||||||
|
- Ghaymah Cloud
|
||||||
|
- Ghaymah CLI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
## Health Endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /health
|
||||||
|
```
|
||||||
|
|
||||||
|
Example response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "healthy"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The monitoring script periodically sends requests to this endpoint to verify application availability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
|
||||||
|
Build the Docker image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t exam-api .
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d -p 5000:5000 exam-api
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Monitoring Script
|
||||||
|
|
||||||
|
The monitoring script (`health-check.py`) executes every **30 seconds** and performs the following operations:
|
||||||
|
|
||||||
|
- Sends an HTTP request to `/health`
|
||||||
|
- Measures response latency
|
||||||
|
- Detects application availability
|
||||||
|
- Records monitoring results
|
||||||
|
- Updates `monitor-log.csv`
|
||||||
|
|
||||||
|
Run the monitor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 health-check.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Monitoring Dashboard
|
||||||
|
|
||||||
|
The dashboard was implemented using HTML, CSS and JavaScript.
|
||||||
|
|
||||||
|
Displayed metrics include:
|
||||||
|
|
||||||
|
- Current application status
|
||||||
|
- Response time
|
||||||
|
- Total requests
|
||||||
|
- Application uptime
|
||||||
|
- Monitoring history
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Deployment using Ghaymah CLI
|
||||||
|
|
||||||
|
The application was deployed to **Ghaymah Cloud** using the official CLI.
|
||||||
|
|
||||||
|
Deployment workflow:
|
||||||
|
|
||||||
|
1. Install Ghaymah CLI
|
||||||
|
2. Authenticate using account credentials
|
||||||
|
3. Select deployment configuration
|
||||||
|
4. Launch the application
|
||||||
|
|
||||||
|
Example commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gy auth login
|
||||||
|
|
||||||
|
cp .ghaymah.production.json .ghaymah.json
|
||||||
|
|
||||||
|
gy resource app launch
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dashboard Preview
|
||||||
|
|
||||||
|
## Local Testing
|
||||||
|
|
||||||
|
The dashboard was first tested locally against the application running on the EC2 instance before deploying to Ghaymah Cloud.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ghaymah Cloud Deployment
|
||||||
|
|
||||||
|
After deployment, the dashboard successfully monitored the live application hosted on **Ghaymah Cloud**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Result
|
||||||
|
|
||||||
|
The project successfully demonstrates:
|
||||||
|
|
||||||
|
- Docker containerization
|
||||||
|
- Cloud deployment on Ghaymah
|
||||||
|
- Automated health monitoring
|
||||||
|
- Response time tracking
|
||||||
|
- Monitoring dashboard
|
||||||
|
- Continuous application health verification
|
||||||
|
|
||||||
|
This implementation satisfies all requirements of **Question 1 – Deploy and Monitor an API on Ghaymah Cloud**.
|
||||||
ثنائية
q1-deploy-monitor/screenshots/dashboard-ghaymah.png
Normal file
ثنائية
q1-deploy-monitor/screenshots/dashboard-ghaymah.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 135 KiB |
ثنائية
q1-deploy-monitor/screenshots/dashboard-local.png
Normal file
ثنائية
q1-deploy-monitor/screenshots/dashboard-local.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 134 KiB |
191
q3-cicd/README.md
Normal file
191
q3-cicd/README.md
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# Q3 – CI/CD Pipeline on Ghaymah Cloud
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This task implements a complete CI/CD pipeline using **GitHub Actions** and **Ghaymah Cloud**.
|
||||||
|
|
||||||
|
The pipeline automatically builds the application, performs a smoke test, deploys to the **staging** environment for development changes, and deploys to the **production** environment after merging into the `main` branch with **manual approval** enabled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pipeline Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
Developer
|
||||||
|
│
|
||||||
|
│ Push
|
||||||
|
▼
|
||||||
|
GitHub Repository
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
GitHub Actions
|
||||||
|
│
|
||||||
|
├── Build Docker Image
|
||||||
|
├── Smoke Test (/health)
|
||||||
|
│
|
||||||
|
├──────────────┐
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
develop main
|
||||||
|
│ │
|
||||||
|
Deploy Manual Approval
|
||||||
|
Staging │
|
||||||
|
▼
|
||||||
|
Deploy Production
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI Pipeline
|
||||||
|
|
||||||
|
The Continuous Integration stage performs:
|
||||||
|
|
||||||
|
- Checkout repository
|
||||||
|
- Build Docker image
|
||||||
|
- Start container
|
||||||
|
- Execute smoke test
|
||||||
|
- Verify `/health` endpoint
|
||||||
|
- Stop and remove the test container
|
||||||
|
|
||||||
|
This ensures that only healthy builds continue to deployment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# CD Pipeline
|
||||||
|
|
||||||
|
## Staging Deployment
|
||||||
|
|
||||||
|
Triggered automatically when code is pushed to the **develop** branch.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
1. Install Ghaymah CLI
|
||||||
|
2. Authenticate using GitHub Secrets
|
||||||
|
3. Load `.ghaymah.staging.json`
|
||||||
|
4. Deploy application to the staging environment
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
- Integration testing
|
||||||
|
- Validation before production
|
||||||
|
- Detect deployment issues early
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
Triggered after merging into the **main** branch.
|
||||||
|
|
||||||
|
Deployment requires **manual approval** through the GitHub Environment protection rules before execution.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
1. Install Ghaymah CLI
|
||||||
|
2. Authenticate using GitHub Secrets
|
||||||
|
3. Load `.ghaymah.production.json`
|
||||||
|
4. Deploy application to the production environment
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
- Stable release
|
||||||
|
- Manual verification before deployment
|
||||||
|
- Reduce production risks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Staging vs Production
|
||||||
|
|
||||||
|
| Staging | Production |
|
||||||
|
|----------|------------|
|
||||||
|
| Testing environment | Live environment |
|
||||||
|
| Used by developers | Used by end users |
|
||||||
|
| Automatic deployment | Protected deployment |
|
||||||
|
| Safe for validation | High availability |
|
||||||
|
| Can be updated frequently | Only verified releases |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manual Approval
|
||||||
|
|
||||||
|
Production deployments are protected using **GitHub Environments**.
|
||||||
|
|
||||||
|
The workflow pauses before deploying to production until manual approval is granted.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
- Prevent accidental deployments
|
||||||
|
- Final verification before release
|
||||||
|
- Safer production deployments
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ghaymah CLI Integration
|
||||||
|
|
||||||
|
Deployment is performed using the official **Ghaymah CLI**.
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gy auth login \
|
||||||
|
--email "<EMAIL>" \
|
||||||
|
--password "<PASSWORD>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .ghaymah.production.json .ghaymah.json
|
||||||
|
gy resource app launch
|
||||||
|
```
|
||||||
|
|
||||||
|
For the staging environment, the workflow uses:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .ghaymah.staging.json .ghaymah.json
|
||||||
|
```
|
||||||
|
|
||||||
|
before deployment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# GitHub Secrets
|
||||||
|
|
||||||
|
Sensitive credentials are stored securely as GitHub Secrets.
|
||||||
|
|
||||||
|
Secrets used:
|
||||||
|
|
||||||
|
- GHAYMAH_EMAIL
|
||||||
|
- GHAYMAH_PW
|
||||||
|
|
||||||
|
No credentials are stored inside the repository.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Technologies Used
|
||||||
|
|
||||||
|
- GitHub Actions
|
||||||
|
- Docker
|
||||||
|
- Ghaymah Cloud
|
||||||
|
- Ghaymah CLI
|
||||||
|
- GitHub Environments
|
||||||
|
- GitHub Secrets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Result
|
||||||
|
|
||||||
|
The pipeline successfully performs:
|
||||||
|
|
||||||
|
- Automated Docker image build
|
||||||
|
- Smoke testing
|
||||||
|
- Automatic deployment to Staging
|
||||||
|
- Manual approval before Production
|
||||||
|
- Production deployment using Ghaymah CLI
|
||||||
|
|
||||||
|
This implementation provides a reliable and production-ready CI/CD workflow.
|
||||||
|
قبل العرض: | الارتفاع: | الحجم: 1.2 MiB بعد العرض: | الارتفاع: | الحجم: 1.2 MiB |
6
q5-mithal-monitor/.dockerignore
Normal file
6
q5-mithal-monitor/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.git
|
||||||
|
monitor-data.json
|
||||||
|
README.md
|
||||||
17
q5-mithal-monitor/.ghaymah.json
Normal file
17
q5-mithal-monitor/.ghaymah.json
Normal 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
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 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
q5-mithal-monitor/README.md
Normal file
161
q5-mithal-monitor/README.md
Normal 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
q5-mithal-monitor/app.py
Normal file
66
q5-mithal-monitor/app.py
Normal 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
q5-mithal-monitor/monitor-data.json
Normal file
692
q5-mithal-monitor/monitor-data.json
Normal 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
q5-mithal-monitor/monitor.py
Normal file
187
q5-mithal-monitor/monitor.py
Normal 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
q5-mithal-monitor/requirements.txt
Normal file
4
q5-mithal-monitor/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Flask==3.1.1
|
||||||
|
requests==2.32.4
|
||||||
|
dnspython==2.7.0
|
||||||
|
gunicorn==23.0.0
|
||||||
280
q5-mithal-monitor/templates/dashboard.html
Normal file
280
q5-mithal-monitor/templates/dashboard.html
Normal 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>
|
||||||
|
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم