diff --git a/common-qabilah/qabilah-profile.txt b/common-qabilah/qabilah-profile.txt
new file mode 100644
index 0000000..8ef5283
--- /dev/null
+++ b/common-qabilah/qabilah-profile.txt
@@ -0,0 +1 @@
+https://qabilah.com/profile/moustafamedhet97/posts
diff --git a/q1-deploy-monitor/Dockerfile b/q1-deploy-monitor/Dockerfile
new file mode 100644
index 0000000..06f1b97
--- /dev/null
+++ b/q1-deploy-monitor/Dockerfile
@@ -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"]
diff --git a/q1-deploy-monitor/app.py b/q1-deploy-monitor/app.py
new file mode 100644
index 0000000..59c70c1
--- /dev/null
+++ b/q1-deploy-monitor/app.py
@@ -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)
diff --git a/q1-deploy-monitor/health-check.sh b/q1-deploy-monitor/health-check.sh
new file mode 100755
index 0000000..d9af605
--- /dev/null
+++ b/q1-deploy-monitor/health-check.sh
@@ -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
diff --git a/q1-deploy-monitor/health.log b/q1-deploy-monitor/health.log
new file mode 100644
index 0000000..ccb5724
--- /dev/null
+++ b/q1-deploy-monitor/health.log
@@ -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
diff --git a/q1-deploy-monitor/requirements.txt b/q1-deploy-monitor/requirements.txt
new file mode 100644
index 0000000..22ac75b
--- /dev/null
+++ b/q1-deploy-monitor/requirements.txt
@@ -0,0 +1 @@
+Flask==3.1.0
diff --git a/q1-deploy-monitor/templates/dashboard.html b/q1-deploy-monitor/templates/dashboard.html
new file mode 100644
index 0000000..d8a15a1
--- /dev/null
+++ b/q1-deploy-monitor/templates/dashboard.html
@@ -0,0 +1,151 @@
+
+
+
+
+
+ Ghaymah API Dashboard
+
+
+
+
+
+
+
+
+
🚀 Ghaymah SRE Dashboard
+
+
+
+
+
+
+
+
+
+
diff --git a/q2-postmortem/postmortem-report.md b/q2-postmortem/postmortem-report.md
new file mode 100644
index 0000000..9613511
--- /dev/null
+++ b/q2-postmortem/postmortem-report.md
@@ -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.
diff --git a/q3-cicd/Dockerfile b/q3-cicd/Dockerfile
new file mode 100644
index 0000000..06f1b97
--- /dev/null
+++ b/q3-cicd/Dockerfile
@@ -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"]
diff --git a/q3-cicd/README.md b/q3-cicd/README.md
new file mode 100644
index 0000000..faf4408
--- /dev/null
+++ b/q3-cicd/README.md
@@ -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://: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://: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.
+
+---
+
diff --git a/q3-cicd/app.py b/q3-cicd/app.py
new file mode 100644
index 0000000..59c70c1
--- /dev/null
+++ b/q3-cicd/app.py
@@ -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)
diff --git a/q3-cicd/requirements.txt b/q3-cicd/requirements.txt
new file mode 100644
index 0000000..22ac75b
--- /dev/null
+++ b/q3-cicd/requirements.txt
@@ -0,0 +1 @@
+Flask==3.1.0
diff --git a/q3-cicd/templates/dashboard.html b/q3-cicd/templates/dashboard.html
new file mode 100644
index 0000000..d230d4f
--- /dev/null
+++ b/q3-cicd/templates/dashboard.html
@@ -0,0 +1,151 @@
+
+
+
+
+
+ Ghaymah SRE Dashboard
+
+
+
+
+
+
+
+
+
🚀 Ghaymah API Dashboard
+
+
+
+
+
+
+
+
+
+
diff --git a/q3-cicd/workflow.yml b/q3-cicd/workflow.yml
new file mode 100644
index 0000000..2da6652
--- /dev/null
+++ b/q3-cicd/workflow.yml
@@ -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
diff --git a/q4-scalability/calculations.md b/q4-scalability/calculations.md
new file mode 100644
index 0000000..38e876f
--- /dev/null
+++ b/q4-scalability/calculations.md
@@ -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 |
diff --git a/q4-scalability/scalable-architecture.png b/q4-scalability/scalable-architecture.png
new file mode 100644
index 0000000..f3e6359
Binary files /dev/null and b/q4-scalability/scalable-architecture.png differ
diff --git a/q5-mithal-monitor/Dockerfile b/q5-mithal-monitor/Dockerfile
new file mode 100644
index 0000000..108b5cf
--- /dev/null
+++ b/q5-mithal-monitor/Dockerfile
@@ -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"]
diff --git a/q5-mithal-monitor/app.js b/q5-mithal-monitor/app.js
new file mode 100644
index 0000000..21b1cdf
--- /dev/null
+++ b/q5-mithal-monitor/app.js
@@ -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 += `
+
+
+
+ | ${row[0]} |
+
+
+
+ ${status ? "UP" : "DOWN"}
+
+ |
+
+ ${row[1]} ms |
+
+ ${row[3]} ms |
+
+ ${row[6]} ms |
+
+ ${row[5]} Days |
+
+
+
+ `;
+
+ });
+
+ }
+
+ 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();
+
diff --git a/q5-mithal-monitor/index.html b/q5-mithal-monitor/index.html
new file mode 100644
index 0000000..686788e
--- /dev/null
+++ b/q5-mithal-monitor/index.html
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+ Mithal Monitoring Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
Mithal Monitoring Dashboard
+
+
+
+
+
+
Uptime (24h)
+
+ 0%
+
+
+
+
+
+
SSL Status
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Last 10 Checks
+
+
+
+
+
+
+
+ | Time |
+ Status |
+ Latency |
+ DNS |
+ Search |
+ SSL Days |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/q5-mithal-monitor/metrics.csv b/q5-mithal-monitor/metrics.csv
new file mode 100644
index 0000000..f30cbd0
--- /dev/null
+++ b/q5-mithal-monitor/metrics.csv
@@ -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
diff --git a/q5-mithal-monitor/monitor.py b/q5-mithal-monitor/monitor.py
new file mode 100644
index 0000000..86519e4
--- /dev/null
+++ b/q5-mithal-monitor/monitor.py
@@ -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()
+
diff --git a/q5-mithal-monitor/monitoring-workflow.yml b/q5-mithal-monitor/monitoring-workflow.yml
new file mode 100644
index 0000000..2833895
--- /dev/null
+++ b/q5-mithal-monitor/monitoring-workflow.yml
@@ -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
diff --git a/q5-mithal-monitor/requirements.txt b/q5-mithal-monitor/requirements.txt
new file mode 100644
index 0000000..c966d64
--- /dev/null
+++ b/q5-mithal-monitor/requirements.txt
@@ -0,0 +1,3 @@
+requests
+pandas
+dnspython
diff --git a/q5-mithal-monitor/style.css b/q5-mithal-monitor/style.css
new file mode 100644
index 0000000..a214e42
--- /dev/null
+++ b/q5-mithal-monitor/style.css
@@ -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;
+ }