adding task files

هذا الالتزام موجود في:
2026-07-29 20:39:34 +00:00
الأصل 87e6534761
التزام 31cba2e547
24 ملفات معدلة مع 1756 إضافات و0 حذوفات

عرض الملف

@@ -0,0 +1 @@
https://qabilah.com/profile/moustafamedhet97/posts

عرض الملف

@@ -0,0 +1,16 @@
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "app.py"]

70
q1-deploy-monitor/app.py Normal file
عرض الملف

@@ -0,0 +1,70 @@
from flask import Flask, jsonify, render_template, request
import time
app = Flask(__name__)
# Application start time
start_time = time.time()
# Metrics
request_count = 0
last_response_time = 0
@app.before_request
def before_request():
request.start_time = time.perf_counter()
@app.after_request
def after_request(response):
global request_count
global last_response_time
request_count += 1
last_response_time = round(
(time.perf_counter() - request.start_time) * 1000, 2
)
return response
@app.route("/")
def home():
return render_template("dashboard.html")
@app.route("/health")
def health():
return jsonify({
"status": "UP",
"message": "Application is healthy",
"uptime_seconds": round(time.time() - start_time, 2),
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0.0"
}), 200
@app.route("/metrics")
def metrics():
return jsonify({
"status": "UP",
"requests": request_count,
"response_time_ms": last_response_time,
"uptime_seconds": round(time.time() - start_time, 2)
})
@app.route("/api/info")
def api_info():
return jsonify({
"application": "Ghaymah SRE API",
"language": "Python",
"framework": "Flask",
"version": "1.0.0",
"author": "Moustafa Medhat"
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)

عرض الملف

@@ -0,0 +1,30 @@
#!/bin/bash
URL="http://localhost:8080/health"
LOG_FILE="/home/ec2-user/projects/ghaymah-exam-moustafa-medhat-sre/q1-deploy-monitor/health.log"
touch "$LOG_FILE"
echo "===== Health Monitor Started =====" >> "$LOG_FILE"
while true
do
START=$(date +%s%3N)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
END=$(date +%s%3N)
RESPONSE_TIME=$((END - START))
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
if [ "$HTTP_CODE" -eq 200 ]; then
STATUS="UP"
else
STATUS="DOWN"
fi
echo "[$TIMESTAMP] Status=$STATUS | HTTP=$HTTP_CODE | Response=${RESPONSE_TIME}ms" >> "$LOG_FILE"
sleep 30
done

عرض الملف

@@ -0,0 +1,29 @@
[2026-07-29 20:25:07] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:25:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:26:07] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:26:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:27:07] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:27:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:28:07] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:28:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:29:07] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:29:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:30:07] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:30:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:31:07] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:31:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:32:07] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:32:37] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:33:07] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:33:37] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:34:07] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:34:38] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:35:08] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:35:38] Status=DOWN | HTTP=000 | Response=8ms
[2026-07-29 20:36:08] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:36:38] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:37:08] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:37:38] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:38:08] Status=DOWN | HTTP=000 | Response=6ms
[2026-07-29 20:38:38] Status=DOWN | HTTP=000 | Response=7ms
[2026-07-29 20:39:08] Status=DOWN | HTTP=000 | Response=7ms

عرض الملف

@@ -0,0 +1 @@
Flask==3.1.0

عرض الملف

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ghaymah API Dashboard</title>
<style>
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family:Arial, Helvetica, sans-serif;
}
body{
background:#f5f7fb;
}
.container{
width:900px;
margin:40px auto;
}
h1{
text-align:center;
margin-bottom:30px;
color:#2c3e50;
}
.cards{
display:flex;
justify-content:space-between;
gap:20px;
}
.card{
flex:1;
background:white;
border-radius:10px;
padding:25px;
text-align:center;
box-shadow:0 5px 15px rgba(0,0,0,.1);
}
.title{
color:#666;
font-size:18px;
margin-bottom:15px;
}
.value{
font-size:34px;
font-weight:bold;
}
.up{
color:green;
}
.down{
color:red;
}
.footer{
margin-top:35px;
text-align:center;
color:#777;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Ghaymah SRE Dashboard</h1>
<div class="cards">
<div class="card">
<div class="title">Status</div>
<div id="status" class="value">Loading...</div>
</div>
<div class="card">
<div class="title">Response Time</div>
<div id="response" class="value">0 ms</div>
</div>
<div class="card">
<div class="title">Requests</div>
<div id="requests" class="value">0</div>
</div>
</div>
<div class="footer">
Last Update:
<span id="time">--</span>
</div>
</div>
<script>
async function loadMetrics(){
try{
const response = await fetch('/metrics');
const data = await response.json();
const status=document.getElementById("status");
status.innerHTML=data.status;
status.className="value";
if(data.status==="UP")
status.classList.add("up");
else
status.classList.add("down");
document.getElementById("response").innerHTML=data.response_time_ms+" ms";
document.getElementById("requests").innerHTML=data.requests;
document.getElementById("time").innerHTML=
new Date().toLocaleTimeString();
}
catch(error){
document.getElementById("status").innerHTML="DOWN";
document.getElementById("status").className="value down";
}
}
loadMetrics();
setInterval(loadMetrics,30000);
</script>
</body>

عرض الملف

@@ -0,0 +1,120 @@
# Postmortem Report
## Incident Summary
**Incident Title:** Repeated OOMKilled Causing Application Downtime
**Date:** 2026-07-28
**Duration:** 45 minutes
**Severity:** High (SEV-1)
**Impact:**
The application became unavailable for 45 minutes because the container was repeatedly terminated with the `OOMKilled` status. During the outage, users were unable to access the application, resulting in service disruption.
---
# Timeline
| Time | Event |
|------|-------|
| 14:00 | Increased traffic caused memory usage to rise. |
| 14:05 | Container exceeded its memory limit and was terminated (OOMKilled). |
| 14:06 | Kubernetes restarted the container automatically. |
| 14:10 - 14:40 | Continuous restart loop occurred due to repeated OOMKilled events. |
| 14:25 | Monitoring system generated high memory usage alerts. |
| 14:35 | SRE team began investigating pod events and resource metrics. |
| 14:45 | Memory limit was increased and the deployment was restarted. |
| 14:45 | Application recovered successfully. |
---
# Root Cause Analysis
### Primary Root Cause
The application exceeded the configured memory limit. When the container reached the limit, the Linux Out Of Memory (OOM) Killer terminated the process.
### Contributing Factors
- Memory limits were configured too low.
- No Horizontal Pod Autoscaler (HPA) was configured.
- Monitoring alerts were triggered after the application became unstable.
- No load testing had been performed before production deployment.
---
# Resolution
The following actions restored the service:
- Increased container memory limits.
- Restarted the deployment.
- Verified application health using `/health`.
- Confirmed stable memory usage.
---
# Preventive Actions
### Immediate
- Increase memory requests and limits.
- Add memory usage alerts.
- Perform load testing before deployment.
### Long-term
- Configure Horizontal Pod Autoscaler (HPA).
- Implement automatic scaling based on CPU and Memory.
- Enable centralized logging.
- Create SLOs and alerting rules.
- Review resource requests for every deployment.
---
# Auto-Scaling Policy
To prevent similar incidents:
- Minimum Replicas: **2**
- Maximum Replicas: **10**
- Target CPU Utilization: **70%**
- Target Memory Utilization: **75%**
- Scale Up immediately when thresholds are exceeded.
- Scale Down gradually after 5 minutes of stable utilization.
- Configure PodDisruptionBudget to maintain availability.
---
# Early Detection Using Ghaymah Monitoring
The issue can be detected early by monitoring:
- Container Memory Usage
- Pod Restart Count
- OOMKilled Events
- Container Health Checks
- Response Time
- Error Rate (5xx)
- Request Rate
- Application Availability
Recommended alerts:
- Memory Usage > 80%
- Restart Count > 3 within 5 minutes
- Health Check Failure
- Response Time > 500 ms
- Availability below 99%
---
# Lessons Learned
- Configure appropriate resource limits.
- Enable proactive monitoring and alerting.
- Test the application under expected production load.
- Implement autoscaling before deploying production workloads.
- Continuously review resource utilization trends.

16
q3-cicd/Dockerfile Normal file
عرض الملف

@@ -0,0 +1,16 @@
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "app.py"]

254
q3-cicd/README.md Normal file
عرض الملف

@@ -0,0 +1,254 @@
# Ghaymah SRE - CI/CD Pipeline Project
## Overview
This project demonstrates a complete CI/CD pipeline using **GitHub Actions**, **Docker**, **Docker Hub**, and **AWS EC2**.
The pipeline automatically builds a Docker image, pushes it to Docker Hub, waits for manual approval before production deployment, and deploys the application to an EC2 instance.
---
# Project Architecture
Developer
GitHub Repository
GitHub Actions
Build Docker Image
Push Image to Docker Hub
Manual Approval
Deploy to AWS EC2
Docker Container
Running Flask Application
---
# Technologies Used
- Git
- GitHub
- GitHub Actions
- Docker
- Docker Hub
- AWS EC2 (Amazon Linux 2023)
- Python Flask
---
# Project Structure
```
.
├── .github
│ └── workflows
│ └── docker-build-push.yml
├── Dockerfile
├── requirements.txt
├── app.py
├── templates/
├── static/
└── README.md
```
---
# CI/CD Workflow
The GitHub Actions workflow performs the following steps:
1. Trigger on every push to the main branch.
2. Checkout the repository.
3. Log in to Docker Hub using GitHub Secrets.
4. Build the Docker image.
5. Push the image to Docker Hub.
6. Wait for manual approval using the Production Environment.
7. Deploy the latest image to the EC2 server.
8. Verify that the application is running successfully.
---
# GitHub Secrets
The following repository secrets were configured:
| Secret | Description |
|---------|-------------|
| DOCKER_USERNAME | Docker Hub username |
| DOCKER_TOKEN | Docker Hub Access Token |
---
# Manual Approval
A GitHub Environment named **production** was created.
Deployment to production requires manual approval before execution.
This prevents accidental deployments and provides an additional safety layer.
---
# Docker Image
The application image is stored in Docker Hub.
Image name:
```
moustafamedhat97/ghaymah-api
```
Each build is tagged using the Git commit SHA.
Example:
```
moustafamedhat97/ghaymah-api:0367751868ea7532b1b22f744baa5e49851158f6
```
---
# Deployment
The deployment process performs the following:
- Pull latest Docker image
- Stop old container (if exists)
- Remove old container
- Start new container
- Expose port 8080
- Restart application
Deployment command:
```bash
docker pull moustafamedhat97/ghaymah-api:latest
docker stop ghaymah-api || true
docker rm ghaymah-api || true
docker run -d \
--name ghaymah-api \
-p 8080:8080 \
moustafamedhat97/ghaymah-api:latest
```
---
# Application Health Check
The application exposes a health endpoint:
```
GET /health
```
Example:
```
http://<EC2-Public-IP>:8080/health
```
Expected response:
```json
{
"status":"UP"
}
```
---
# Dashboard
The deployed application provides a dashboard displaying:
- Application Status
- Response Time
- Total Requests
- Last Update Time
Example:
```
http://<EC2-Public-IP>:8080
```
---
# Difference Between Staging and Production
## Staging
- Used for testing before release.
- Mirrors the production environment.
- Safe for validation and QA.
- Can contain test data.
## Production
- Live environment.
- Used by end users.
- Requires high availability.
- Requires manual approval before deployment.
---
# Ghaymah CLI Integration
The original requirement requested deployment using the Ghaymah CLI.
Since the CLI was not available in the execution environment, deployment was simulated using Docker commands.
The integration steps would normally be:
1. Install Ghaymah CLI.
2. Authenticate using:
```bash
ghaymah login
```
3. Configure the target project.
4. Deploy the application:
```bash
ghaymah deploy
```
---
# Result
The CI/CD pipeline was successfully implemented.
Achievements:
- Docker image successfully built.
- Image pushed to Docker Hub.
- Manual approval configured.
- Application deployed to AWS EC2.
- Docker container started successfully.
- Flask application became accessible.
- Health checks passed.
- Dashboard successfully displayed through the browser.
---

70
q3-cicd/app.py Normal file
عرض الملف

@@ -0,0 +1,70 @@
from flask import Flask, jsonify, render_template, request
import time
app = Flask(__name__)
# Application start time
start_time = time.time()
# Metrics
request_count = 0
last_response_time = 0
@app.before_request
def before_request():
request.start_time = time.perf_counter()
@app.after_request
def after_request(response):
global request_count
global last_response_time
request_count += 1
last_response_time = round(
(time.perf_counter() - request.start_time) * 1000, 2
)
return response
@app.route("/")
def home():
return render_template("dashboard.html")
@app.route("/health")
def health():
return jsonify({
"status": "UP",
"message": "Application is healthy",
"uptime_seconds": round(time.time() - start_time, 2),
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0.0"
}), 200
@app.route("/metrics")
def metrics():
return jsonify({
"status": "UP",
"requests": request_count,
"response_time_ms": last_response_time,
"uptime_seconds": round(time.time() - start_time, 2)
})
@app.route("/api/info")
def api_info():
return jsonify({
"application": "Ghaymah SRE API",
"language": "Python",
"framework": "Flask",
"version": "1.0.0",
"author": "Moustafa Medhat"
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)

1
q3-cicd/requirements.txt Normal file
عرض الملف

@@ -0,0 +1 @@
Flask==3.1.0

عرض الملف

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ghaymah SRE Dashboard</title>
<style>
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family:Arial, Helvetica, sans-serif;
}
body{
background:#f5f7fb;
}
.container{
width:900px;
margin:40px auto;
}
h1{
text-align:center;
margin-bottom:30px;
color:#2c3e50;
}
.cards{
display:flex;
justify-content:space-between;
gap:20px;
}
.card{
flex:1;
background:white;
border-radius:10px;
padding:25px;
text-align:center;
box-shadow:0 5px 15px rgba(0,0,0,.1);
}
.title{
color:#666;
font-size:18px;
margin-bottom:15px;
}
.value{
font-size:34px;
font-weight:bold;
}
.up{
color:green;
}
.down{
color:red;
}
.footer{
margin-top:35px;
text-align:center;
color:#777;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Ghaymah API Dashboard</h1>
<div class="cards">
<div class="card">
<div class="title">Status</div>
<div id="status" class="value">Loading...</div>
</div>
<div class="card">
<div class="title">Response Time</div>
<div id="response" class="value">0 ms</div>
</div>
<div class="card">
<div class="title">Requests</div>
<div id="requests" class="value">0</div>
</div>
</div>
<div class="footer">
Last Update:
<span id="time">--</span>
</div>
</div>
<script>
async function loadMetrics(){
try{
const response = await fetch('/metrics');
const data = await response.json();
const status=document.getElementById("status");
status.innerHTML=data.status;
status.className="value";
if(data.status==="UP")
status.classList.add("up");
else
status.classList.add("down");
document.getElementById("response").innerHTML=data.response_time_ms+" ms";
document.getElementById("requests").innerHTML=data.requests;
document.getElementById("time").innerHTML=
new Date().toLocaleTimeString();
}
catch(error){
document.getElementById("status").innerHTML="DOWN";
document.getElementById("status").className="value down";
}
}
loadMetrics();
setInterval(loadMetrics,30000);
</script>
</body>

59
q3-cicd/workflow.yml Normal file
عرض الملف

@@ -0,0 +1,59 @@
name: Build, Push and Deploy
on:
push:
branches:
- main
jobs:
build:
name: Build and Push Docker Image
runs-on: self-hosted
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build Docker Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/ghaymah-api:${{ github.sha }} .
- name: Push Docker Image
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/ghaymah-api:${{ github.sha }}
deploy-production:
name: Deploy to Production
needs: build
runs-on: self-hosted
environment:
name: production
steps:
- name: Pull Latest Image
run: |
docker pull ${{ secrets.DOCKER_USERNAME }}/ghaymah-api:${{ github.sha }}
- name: Stop Existing Container
run: |
docker stop ghaymah-api || true
docker rm ghaymah-api || true
- name: Run New Container
run: |
docker run -d \
--name ghaymah-api \
--restart unless-stopped \
-p 8080:8080 \
${{ secrets.DOCKER_USERNAME }}/ghaymah-api:${{ github.sha }}
- name: Verify Deployment
run: |
docker ps

عرض الملف

@@ -0,0 +1,74 @@
# Scalability and Load Balancing
## 1. Container Calculation
### Given
- Expected traffic: **15,000 requests/second**
- One container capacity: **500 requests/second**
- Safety margin: **30%**
### Step 1: Base Number of Containers
15000 / 500 = **30 containers**
### Step 2: Add Safety Margin
30 × 1.30 = **39 containers**
### Final Result
**39 containers** are required to safely handle 15,000 requests per second while maintaining a 30% capacity margin for traffic spikes and failover scenarios.
---
# 2. Cold Start Strategy
To minimize startup latency for new containers:
- Keep a small pool of warm containers ready to receive traffic.
- Use Kubernetes Horizontal Pod Autoscaler (HPA).
- Pre-pull container images on worker nodes.
- Configure Readiness Probes before accepting requests.
- Use Rolling Updates for zero-downtime deployments.
- Scale based on CPU utilization and request rate.
---
# 3. Ghaymah Block Storage for Stateful Workloads
Containers are ephemeral and lose local storage when restarted. Stateful workloads require persistent storage.
Ghaymah Block Storage provides persistent volumes that remain attached to applications even after pod or container recreation.
Typical stateful workloads include:
- PostgreSQL
- MySQL
- MongoDB
- Redis Persistence
- Elasticsearch
- Jenkins
- Kafka
- RabbitMQ
Benefits:
- Persistent storage
- High performance
- Volume reattachment after restart
- Snapshot support
- Backup and disaster recovery
- Independent lifecycle from containers
---
# Summary
| Requirement | Solution |
|------------|----------|
| Incoming Traffic | 15,000 req/s |
| Container Capacity | 500 req/s |
| Safety Margin | 30% |
| Required Containers | **39 Containers** |
| Cold Start | Warm Pool, HPA, Image Pre-Pulling, Readiness Probes |

ثنائية
q4-scalability/scalable-architecture.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 1.3 MiB

عرض الملف

@@ -0,0 +1,21 @@
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir \
--prefix=/install \
-r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
EXPOSE 8000
CMD ["python3", "-m", "http.server", "8000", "--directory", "/app"]

230
q5-mithal-monitor/app.js Normal file
عرض الملف

@@ -0,0 +1,230 @@
let chart = null;
async function loadData() {
try {
const response = await fetch("../metrics.csv?t=" + Date.now());
const text = await response.text();
let rows = text.trim().split("\n");
if (rows.length <= 1) return;
// Remove CSV header
rows.shift();
const data = rows
.map(row => row.split(","))
.filter(row => row.length >= 7);
const history = document.getElementById("history");
history.innerHTML = "";
const now = new Date();
// ==========================
// Uptime (24h)
// ==========================
const last24 = data.filter(row => {
const t = new Date(row[0].replace(" ", "T"));
return (now - t) <= (24 * 60 * 60 * 1000);
});
const up = last24.filter(row => row[2] === "200").length;
const uptime =
last24.length === 0
? 0
: ((up / last24.length) * 100).toFixed(2);
document.getElementById("uptime").textContent = uptime + "%";
// ==========================
// SSL Status
// ==========================
const latest = data[data.length - 1];
document.getElementById("ssl").textContent =
latest[5] + " Days Remaining";
// ==========================
// Chart
// ==========================
let labels = [];
let values = [];
data.forEach(row => {
const latency = parseFloat(row[1]);
// Ignore abnormal values
if (latency <= 500) {
labels.push(row[0].split(" ")[1]);
values.push(latency);
}
});
drawChart(labels, values);
// ==========================
// Last 10 Checks
// ==========================
data.slice(-10).reverse().forEach(row => {
const status = row[2] === "200";
history.innerHTML += `
<tr>
<td>${row[0]}</td>
<td class="${status ? "up" : "down"}">
${status ? "UP" : "DOWN"}
</td>
<td>${row[1]} ms</td>
<td>${row[3]} ms</td>
<td>${row[6]} ms</td>
<td>${row[5]} Days</td>
</tr>
`;
});
}
catch (err) {
console.error("Dashboard Error:", err);
}
}
function drawChart(labels, values) {
const canvas = document.getElementById("latencyChart");
if (!canvas) return;
if (chart) {
chart.destroy();
}
chart = new Chart(canvas, {
type: "line",
data: {
labels: labels,
datasets: [
{
label: "Latency (ms)",
data: values,
borderColor: "#3498db",
backgroundColor: "rgba(52,152,219,0.15)",
borderWidth: 2,
fill: true,
tension: 0.3,
pointRadius: 3,
pointHoverRadius: 5
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: true
}
},
scales: {
y: {
min: 0,
max: 250,
title: {
display: true,
text: "Milliseconds"
}
},
x: {
title: {
display: true,
text: "Time"
}
}
}
}
});
}
loadData();

عرض الملف

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mithal Monitoring Dashboard</title>
<link rel="stylesheet" href="style.css?v=3">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="container">
<h1>Mithal Monitoring Dashboard</h1>
<div class="cards">
<div class="card">
<h3>Uptime (24h)</h3>
<h2 id="uptime">0%</h2>
</div>
<div class="card">
<h3>SSL Status</h3>
<h2 id="ssl">Loading...</h2>
</div>
</div>
<div class="chart-container">
<canvas id="latencyChart"></canvas>
</div>
<div class="table-container">
<h2>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>
</div>
<script src="app.js?v=3"></script>
</body>

عرض الملف

@@ -0,0 +1,53 @@
Timestamp,Latency(ms),Status,DNS(ms),SSL Expiry,Days Left,Search(ms)
2026-07-29 12:29:42,86.56,200,1.24,2026-09-15,48,77.74
2026-07-29 13:02:47,115.84,200,0.63,2026-09-15,48,76.28
2026-07-29 13:03:48,77.67,200,0.35,2026-09-15,48,75.12
2026-07-29 13:04:48,81.49,200,0.94,2026-09-15,48,83.27
2026-07-29 13:05:48,76.87,200,0.88,2026-09-15,48,79.01
2026-07-29 13:06:48,105.08,200,0.47,2026-09-15,48,75.93
2026-07-29 13:07:49,91.23,200,0.39,2026-09-15,48,74.53
2026-07-29 13:08:49,87.99,200,0.38,2026-09-15,48,86.44
2026-07-29 13:09:49,78.44,200,0.6,2026-09-15,48,78.73
2026-07-29 13:10:50,75.26,200,0.45,2026-09-15,47,75.22
2026-07-29 13:11:50,78.65,200,0.4,2026-09-15,47,78.07
2026-07-29 13:12:50,82.63,200,0.31,2026-09-15,47,98.73
2026-07-29 13:13:50,76.69,200,0.38,2026-09-15,47,76.14
2026-07-29 13:14:51,78.32,200,0.29,2026-09-15,47,79.26
2026-07-29 13:15:51,78.01,200,0.34,2026-09-15,47,77.86
2026-07-29 13:16:51,78.61,200,2.42,2026-09-15,47,75.91
2026-07-29 13:17:51,112.39,200,0.39,2026-09-15,47,78.11
2026-07-29 13:18:52,76.81,200,3.9,2026-09-15,47,79.26
2026-07-29 13:19:52,76.79,200,0.29,2026-09-15,47,81.39
2026-07-29 13:20:52,81.92,200,0.38,2026-09-15,47,81.31
2026-07-29 13:21:52,79.67,200,0.39,2026-09-15,47,75.22
2026-07-29 13:22:53,91.11,200,0.35,2026-09-15,47,77.56
2026-07-29 13:23:53,85.6,200,4.12,2026-09-15,47,75.11
2026-07-29 13:24:53,79.27,200,0.36,2026-09-15,47,80.63
2026-07-29 13:25:53,77.88,200,0.32,2026-09-15,47,82.34
2026-07-29 13:26:54,79.35,200,0.49,2026-09-15,47,74.78
2026-07-29 13:27:54,96.65,200,0.37,2026-09-15,47,75.99
2026-07-29 13:28:54,81.67,200,1.55,2026-09-15,47,72.96
2026-07-29 13:29:54,96.08,200,4.82,2026-09-15,47,77.69
2026-07-29 13:30:56,931.52,200,0.33,2026-09-15,47,83.28
2026-07-29 13:31:56,80.81,200,1.11,2026-09-15,47,79.18
2026-07-29 13:32:56,156.07,200,0.5,2026-09-15,47,77.07
2026-07-29 13:35:19,84.33,200,1.81,2026-09-15,47,89.93
2026-07-29 13:36:20,82.3,200,0.45,2026-09-15,47,76.14
2026-07-29 13:37:20,81.27,200,0.38,2026-09-15,47,78.65
2026-07-29 13:38:20,176.09,200,0.53,2026-09-15,47,102.99
2026-07-29 13:46:29,92.47,200,0.31,2026-09-15,47,73.5
2026-07-29 13:47:29,79.73,200,0.32,2026-09-15,47,79.24
2026-07-29 13:48:29,76.52,200,0.64,2026-09-15,47,79.0
2026-07-29 13:49:29,80.01,200,0.55,2026-09-15,47,78.53
2026-07-29 13:50:30,78.15,200,1.62,2026-09-15,47,75.87
2026-07-29 13:51:30,135.51,200,0.43,2026-09-15,47,79.1
2026-07-29 13:52:30,75.61,200,0.36,2026-09-15,47,77.5
2026-07-29 13:53:31,77.33,200,0.38,2026-09-15,47,120.55
2026-07-29 13:54:31,77.05,200,0.57,2026-09-15,47,77.96
2026-07-29 13:55:31,78.7,200,0.4,2026-09-15,47,72.49
2026-07-29 13:56:31,100.53,200,0.36,2026-09-15,47,76.88
2026-07-29 13:57:32,82.96,200,0.38,2026-09-15,47,86.56
2026-07-29 13:58:32,96.16,200,0.45,2026-09-15,47,74.9
2026-07-29 13:59:32,79.9,200,0.32,2026-09-15,47,74.98
2026-07-29 14:00:32,80.24,200,1.81,2026-09-15,47,73.09
2026-07-29 14:01:33,130.87,200,0.35,2026-09-15,47,76.39
1 Timestamp Latency(ms) Status DNS(ms) SSL Expiry Days Left Search(ms)
2 2026-07-29 12:29:42 86.56 200 1.24 2026-09-15 48 77.74
3 2026-07-29 13:02:47 115.84 200 0.63 2026-09-15 48 76.28
4 2026-07-29 13:03:48 77.67 200 0.35 2026-09-15 48 75.12
5 2026-07-29 13:04:48 81.49 200 0.94 2026-09-15 48 83.27
6 2026-07-29 13:05:48 76.87 200 0.88 2026-09-15 48 79.01
7 2026-07-29 13:06:48 105.08 200 0.47 2026-09-15 48 75.93
8 2026-07-29 13:07:49 91.23 200 0.39 2026-09-15 48 74.53
9 2026-07-29 13:08:49 87.99 200 0.38 2026-09-15 48 86.44
10 2026-07-29 13:09:49 78.44 200 0.6 2026-09-15 48 78.73
11 2026-07-29 13:10:50 75.26 200 0.45 2026-09-15 47 75.22
12 2026-07-29 13:11:50 78.65 200 0.4 2026-09-15 47 78.07
13 2026-07-29 13:12:50 82.63 200 0.31 2026-09-15 47 98.73
14 2026-07-29 13:13:50 76.69 200 0.38 2026-09-15 47 76.14
15 2026-07-29 13:14:51 78.32 200 0.29 2026-09-15 47 79.26
16 2026-07-29 13:15:51 78.01 200 0.34 2026-09-15 47 77.86
17 2026-07-29 13:16:51 78.61 200 2.42 2026-09-15 47 75.91
18 2026-07-29 13:17:51 112.39 200 0.39 2026-09-15 47 78.11
19 2026-07-29 13:18:52 76.81 200 3.9 2026-09-15 47 79.26
20 2026-07-29 13:19:52 76.79 200 0.29 2026-09-15 47 81.39
21 2026-07-29 13:20:52 81.92 200 0.38 2026-09-15 47 81.31
22 2026-07-29 13:21:52 79.67 200 0.39 2026-09-15 47 75.22
23 2026-07-29 13:22:53 91.11 200 0.35 2026-09-15 47 77.56
24 2026-07-29 13:23:53 85.6 200 4.12 2026-09-15 47 75.11
25 2026-07-29 13:24:53 79.27 200 0.36 2026-09-15 47 80.63
26 2026-07-29 13:25:53 77.88 200 0.32 2026-09-15 47 82.34
27 2026-07-29 13:26:54 79.35 200 0.49 2026-09-15 47 74.78
28 2026-07-29 13:27:54 96.65 200 0.37 2026-09-15 47 75.99
29 2026-07-29 13:28:54 81.67 200 1.55 2026-09-15 47 72.96
30 2026-07-29 13:29:54 96.08 200 4.82 2026-09-15 47 77.69
31 2026-07-29 13:30:56 931.52 200 0.33 2026-09-15 47 83.28
32 2026-07-29 13:31:56 80.81 200 1.11 2026-09-15 47 79.18
33 2026-07-29 13:32:56 156.07 200 0.5 2026-09-15 47 77.07
34 2026-07-29 13:35:19 84.33 200 1.81 2026-09-15 47 89.93
35 2026-07-29 13:36:20 82.3 200 0.45 2026-09-15 47 76.14
36 2026-07-29 13:37:20 81.27 200 0.38 2026-09-15 47 78.65
37 2026-07-29 13:38:20 176.09 200 0.53 2026-09-15 47 102.99
38 2026-07-29 13:46:29 92.47 200 0.31 2026-09-15 47 73.5
39 2026-07-29 13:47:29 79.73 200 0.32 2026-09-15 47 79.24
40 2026-07-29 13:48:29 76.52 200 0.64 2026-09-15 47 79.0
41 2026-07-29 13:49:29 80.01 200 0.55 2026-09-15 47 78.53
42 2026-07-29 13:50:30 78.15 200 1.62 2026-09-15 47 75.87
43 2026-07-29 13:51:30 135.51 200 0.43 2026-09-15 47 79.1
44 2026-07-29 13:52:30 75.61 200 0.36 2026-09-15 47 77.5
45 2026-07-29 13:53:31 77.33 200 0.38 2026-09-15 47 120.55
46 2026-07-29 13:54:31 77.05 200 0.57 2026-09-15 47 77.96
47 2026-07-29 13:55:31 78.7 200 0.4 2026-09-15 47 72.49
48 2026-07-29 13:56:31 100.53 200 0.36 2026-09-15 47 76.88
49 2026-07-29 13:57:32 82.96 200 0.38 2026-09-15 47 86.56
50 2026-07-29 13:58:32 96.16 200 0.45 2026-09-15 47 74.9
51 2026-07-29 13:59:32 79.9 200 0.32 2026-09-15 47 74.98
52 2026-07-29 14:00:32 80.24 200 1.81 2026-09-15 47 73.09
53 2026-07-29 14:01:33 130.87 200 0.35 2026-09-15 47 76.39

عرض الملف

@@ -0,0 +1,142 @@
import requests
import socket
import ssl
import time
import csv
import os
from datetime import datetime
HOST = "mithal.space"
URL = f"https://{HOST}"
SEARCH_URL = f"{URL}/?q=test"
CSV_FILE = "metrics.csv"
def check_latency():
start = time.time()
response = requests.get(URL, timeout=10)
latency = round((time.time() - start) * 1000, 2)
return latency, response.status_code
def check_dns():
start = time.time()
socket.gethostbyname(HOST)
dns_time = round((time.time() - start) * 1000, 2)
return dns_time
def check_ssl():
context = ssl.create_default_context()
with context.wrap_socket(
socket.socket(),
server_hostname=HOST
) as s:
s.settimeout(10)
s.connect((HOST, 443))
cert = s.getpeercert()
expire = cert["notAfter"]
expire_date = datetime.strptime(
expire,
"%b %d %H:%M:%S %Y %Z"
)
remaining = (expire_date - datetime.utcnow()).days
return expire_date.strftime("%Y-%m-%d"), remaining
def check_search():
start = time.time()
requests.get(SEARCH_URL, timeout=10)
search_latency = round(
(time.time() - start) * 1000,
2
)
return search_latency
def save_csv(data):
file_exists = os.path.isfile(CSV_FILE)
with open(CSV_FILE, "a", newline="") as file:
writer = csv.writer(file)
if not file_exists:
writer.writerow([
"Timestamp",
"Latency(ms)",
"Status",
"DNS(ms)",
"SSL Expiry",
"Days Left",
"Search(ms)"
])
writer.writerow(data)
def monitor():
while True:
try:
latency, status = check_latency()
dns = check_dns()
ssl_date, ssl_days = check_ssl()
search = check_search()
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
row = [
now,
latency,
status,
dns,
ssl_date,
ssl_days,
search
]
save_csv(row)
print(row)
except Exception as e:
print("Monitoring Error:", e)
print("Waiting 60 seconds...\n")
time.sleep(60)
if __name__ == "__main__":
monitor()

عرض الملف

@@ -0,0 +1,59 @@
name: Build, Push and Deploy
on:
push:
branches:
- main
jobs:
build:
name: Build and Push Docker Image
runs-on: self-hosted
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build Docker Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/mithal-monitor:${{ github.sha }} .
- name: Push Docker Image
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/mithal-monitor:${{ github.sha }}
deploy-production:
name: Deploy to Production
needs: build
runs-on: self-hosted
environment:
name: production
steps:
- name: Pull Latest Image
run: |
docker pull ${{ secrets.DOCKER_USERNAME }}/mithal-monitor:${{ github.sha }}
- name: Stop Existing Container
run: |
docker stop mithal-monitor || true
docker rm mithal-monitor || true
- name: Run New Container
run: |
docker run -d \
--name mithal-monitor \
--restart unless-stopped \
-p 8000:8000 \
${{ secrets.DOCKER_USERNAME }}/mithal-monitor:${{ github.sha }}
- name: Verify Deployment
run: |
docker ps

عرض الملف

@@ -0,0 +1,3 @@
requests
pandas
dnspython

121
q5-mithal-monitor/style.css Normal file
عرض الملف

@@ -0,0 +1,121 @@
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family:Arial, Helvetica, sans-serif;
}
body{
background:#f4f7fb;
color:#333;
}
.container{
width:95%;
max-width:1200px;
margin:30px auto;
}
h1{
text-align:center;
margin-bottom:30px;
color:#2c3e50;
}
.cards{
display:flex;
gap:20px;
justify-content:center;
margin-bottom:30px;
flex-wrap:wrap;
}
.card{
background:#fff;
width:280px;
padding:25px;
border-radius:12px;
text-align:center;
box-shadow:0 5px 15px rgba(0,0,0,.08);
}
.card h3{
color:#666;
margin-bottom:15px;
}
.card h2{
color:#2c3e50;
font-size:32px;
}
.chart-container{
background:#fff;
padding:20px;
border-radius:12px;
box-shadow:0 5px 15px rgba(0,0,0,.08);
margin-bottom:30px;
height:450px;
}
.table-container{
background:#fff;
padding:20px;
border-radius:12px;
box-shadow:0 5px 15px rgba(0,0,0,.08);
}
.table-container h2{
margin-bottom:20px;
}
table{
width:100%;
border-collapse:collapse;
}
th{
background:#3498db;
color:white;
padding:12px;
}
td{
padding:12px;
border-bottom:1px solid #ddd;
text-align:center;
}
tr:hover{
background:#f8f8f8;
}
.up{
color:green;
font-weight:bold;
}
.down{
color:red;
font-weight:bold;
}
canvas{
width:100% !important;
height:100% !important;
}
@media(max-width:768px){
.cards{
flex-direction:column;
align-items:center;
}
table{
font-size:14px;
}
.chart-container{
height:350px;
}