Refactor code structure for improved readability and maintainability
13
Q1/Dashboard/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
# Minimal static-file server for just the dashboard, so it can be deployed
|
||||||
|
# as its own service on Ghaymah, separate from the API container.
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# nginx's default port is 80; most container platforms (Ghaymah included,
|
||||||
|
# per its port-configuration step) let you pick which port the service
|
||||||
|
# listens on, so we keep nginx's default here and just set that port
|
||||||
|
# when creating the service.
|
||||||
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
213
Q1/Dashboard/index.html
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ar" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>لوحة مراقبة الـ API</title>
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#f4f7f6;
|
||||||
|
--card:#ffffff;
|
||||||
|
--border:#e3e8ec;
|
||||||
|
--text:#25313c;
|
||||||
|
--muted:#7a8894;
|
||||||
|
--accent:#0056b3;
|
||||||
|
--up:#28a745;
|
||||||
|
--down:#dc3545;
|
||||||
|
--warn:#f0ad4e;
|
||||||
|
}
|
||||||
|
*{ box-sizing:border-box; }
|
||||||
|
body{
|
||||||
|
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background:var(--bg);
|
||||||
|
display:flex; justify-content:center; align-items:center;
|
||||||
|
min-height:100vh; margin:0; padding:24px;
|
||||||
|
}
|
||||||
|
.dashboard{
|
||||||
|
background:var(--card);
|
||||||
|
padding:30px;
|
||||||
|
border-radius:14px;
|
||||||
|
box-shadow:0 8px 24px rgba(15,35,55,0.08);
|
||||||
|
text-align:center;
|
||||||
|
width:420px;
|
||||||
|
max-width:100%;
|
||||||
|
border:1px solid var(--border);
|
||||||
|
}
|
||||||
|
h2{
|
||||||
|
color:var(--text);
|
||||||
|
margin:0 0 22px;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
gap:10px;
|
||||||
|
font-size:1.25rem;
|
||||||
|
}
|
||||||
|
.status-dot{
|
||||||
|
width:12px; height:12px; border-radius:50%;
|
||||||
|
background:#9aa5ad;
|
||||||
|
box-shadow:0 0 0 0 rgba(154,165,173,0.5);
|
||||||
|
flex-shrink:0;
|
||||||
|
}
|
||||||
|
.status-dot.online{ background:var(--up); animation:pulse-up 2s infinite; }
|
||||||
|
.status-dot.offline{ background:var(--down); animation:pulse-down 2s infinite; }
|
||||||
|
@keyframes pulse-up{
|
||||||
|
0%{ box-shadow:0 0 0 0 rgba(40,167,69,0.55); }
|
||||||
|
70%{ box-shadow:0 0 0 9px rgba(40,167,69,0); }
|
||||||
|
100%{ box-shadow:0 0 0 0 rgba(40,167,69,0); }
|
||||||
|
}
|
||||||
|
@keyframes pulse-down{
|
||||||
|
0%{ box-shadow:0 0 0 0 rgba(220,53,69,0.55); }
|
||||||
|
70%{ box-shadow:0 0 0 9px rgba(220,53,69,0); }
|
||||||
|
100%{ box-shadow:0 0 0 0 rgba(220,53,69,0); }
|
||||||
|
}
|
||||||
|
.metric{
|
||||||
|
background:#eef2f5;
|
||||||
|
margin:10px 0;
|
||||||
|
padding:14px 16px;
|
||||||
|
border-radius:9px;
|
||||||
|
font-size:1.05rem;
|
||||||
|
display:flex;
|
||||||
|
justify-content:space-between;
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
.metric span{ font-weight:700; color:var(--accent); font-variant-numeric:tabular-nums; }
|
||||||
|
.metric span.status-online{ color:var(--up); }
|
||||||
|
.metric span.status-offline{ color:var(--down); }
|
||||||
|
.meta-row{
|
||||||
|
margin-top:16px;
|
||||||
|
font-size:0.75rem;
|
||||||
|
color:var(--muted);
|
||||||
|
display:flex;
|
||||||
|
justify-content:space-between;
|
||||||
|
}
|
||||||
|
.api-row{
|
||||||
|
margin-top:18px;
|
||||||
|
padding-top:14px;
|
||||||
|
border-top:1px dashed var(--border);
|
||||||
|
text-align:right;
|
||||||
|
}
|
||||||
|
.api-row label{
|
||||||
|
display:block;
|
||||||
|
font-size:0.72rem;
|
||||||
|
color:var(--muted);
|
||||||
|
margin-bottom:6px;
|
||||||
|
}
|
||||||
|
.api-row .input-group{
|
||||||
|
display:flex;
|
||||||
|
gap:6px;
|
||||||
|
}
|
||||||
|
.api-row input{
|
||||||
|
flex:1;
|
||||||
|
padding:8px 10px;
|
||||||
|
border:1px solid var(--border);
|
||||||
|
border-radius:7px;
|
||||||
|
font-size:0.8rem;
|
||||||
|
direction:ltr;
|
||||||
|
text-align:left;
|
||||||
|
}
|
||||||
|
.api-row button{
|
||||||
|
padding:8px 14px;
|
||||||
|
border:none;
|
||||||
|
border-radius:7px;
|
||||||
|
background:var(--accent);
|
||||||
|
color:#fff;
|
||||||
|
font-size:0.8rem;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
.api-row button:hover{ opacity:0.9; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="dashboard">
|
||||||
|
<h2>مراقبة النظام <span id="statusDot" class="status-dot"></span></h2>
|
||||||
|
|
||||||
|
<div class="metric">
|
||||||
|
الحالة: <span id="statusText">جاري التحميل...</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
زمن الاستجابة: <span id="responseTime">— ms</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
عدد الطلبات الإجمالي: <span id="requestCount">—</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meta-row">
|
||||||
|
<span id="uptimeText">التشغيل: —</span>
|
||||||
|
<span id="lastUpdated">آخر تحديث: —</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-row">
|
||||||
|
<label for="apiInput">عنوان الـ API (health endpoint):</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="apiInput" type="text" placeholder="https://backend-06febdb2d9f4.hosted.ghaymah.systems/health">
|
||||||
|
<button id="apiSave">حفظ</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// This copy of the dashboard is meant to be hosted on its own — separately
|
||||||
|
// from the API — so it needs to be told where the API lives. Resolution
|
||||||
|
// order:
|
||||||
|
// 1. ?api=<url> query-string param, e.g. index.html?api=https://foo/health
|
||||||
|
// 2. the value typed into the input box below (kept only in memory /
|
||||||
|
// in the URL — nothing is written to disk or browser storage)
|
||||||
|
// 3. falls back to the hosted backend URL provided for this project.
|
||||||
|
const POLL_MS = 5000;
|
||||||
|
const DEFAULT_HEALTH_URL = 'https://backend-06febdb2d9f4.hosted.ghaymah.systems/health';
|
||||||
|
let healthUrl = new URLSearchParams(window.location.search).get('api') || DEFAULT_HEALTH_URL;
|
||||||
|
|
||||||
|
const apiInput = document.getElementById('apiInput');
|
||||||
|
apiInput.value = healthUrl === '/health' ? '' : healthUrl;
|
||||||
|
|
||||||
|
document.getElementById('apiSave').addEventListener('click', () => {
|
||||||
|
const val = apiInput.value.trim();
|
||||||
|
if (val) {
|
||||||
|
healthUrl = val;
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set('api', val);
|
||||||
|
window.history.replaceState({}, '', url); // shareable link, no storage used
|
||||||
|
fetchMetrics();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function fmtUptime(seconds){
|
||||||
|
if (seconds === undefined || seconds === null) return '—';
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = Math.floor(seconds % 60);
|
||||||
|
return `${h}h ${m}m ${s}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMetrics(){
|
||||||
|
try {
|
||||||
|
const res = await fetch(healthUrl, { cache: 'no-store' });
|
||||||
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const statusEl = document.getElementById('statusText');
|
||||||
|
statusEl.innerText = data.status || 'Online';
|
||||||
|
statusEl.className = 'status-online';
|
||||||
|
document.getElementById('statusDot').className = 'status-dot online';
|
||||||
|
|
||||||
|
document.getElementById('responseTime').innerText = (data.response_time_ms ?? 0) + ' ms';
|
||||||
|
document.getElementById('requestCount').innerText = data.request_count ?? '—';
|
||||||
|
document.getElementById('uptimeText').innerText = 'التشغيل: ' + fmtUptime(data.uptime_seconds);
|
||||||
|
document.getElementById('lastUpdated').innerText =
|
||||||
|
'آخر تحديث: ' + new Date().toLocaleTimeString('ar-EG');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
const statusEl = document.getElementById('statusText');
|
||||||
|
statusEl.innerText = 'انقطاع الاتصال (Offline)';
|
||||||
|
statusEl.className = 'status-offline';
|
||||||
|
document.getElementById('statusDot').className = 'status-dot offline';
|
||||||
|
document.getElementById('responseTime').innerText = '—';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMetrics();
|
||||||
|
setInterval(fetchMetrics, POLL_MS);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
84
Q1/README.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# API + Health Monitor + Dashboard (for ghaymah.systems)
|
||||||
|
|
||||||
|
This is your uploaded project (`server.js`, `monitor.sh`, `index.html`,
|
||||||
|
`Dockerfile`) with three functional bugs fixed and a couple of production
|
||||||
|
hardening tweaks. Nothing about your architecture changed.
|
||||||
|
|
||||||
|
## What was wrong and what I changed
|
||||||
|
|
||||||
|
1. **`index.html` was fetching the wrong URL.**
|
||||||
|
`const API_URL = "";` then `fetch(API_URL)` calls the *current page*
|
||||||
|
(`/`), not `/health` — so it was trying to `JSON.parse()` an HTML page
|
||||||
|
and always fell into the `catch` block ("Offline"), even when the API
|
||||||
|
was healthy. Fixed to `fetch('/health')`.
|
||||||
|
|
||||||
|
2. **`server.js` had no route serving `index.html`.**
|
||||||
|
The dashboard needs to be reachable somewhere. I added
|
||||||
|
`express.static('public')` and moved `index.html` into `public/`, so the
|
||||||
|
dashboard is served at `/` and the plain-text welcome message moved to
|
||||||
|
`/api` so the two don't collide.
|
||||||
|
|
||||||
|
3. **`monitor.sh` referenced `$response_time`, which was never set** — the
|
||||||
|
response-time column was always empty. Fixed by measuring it directly
|
||||||
|
with `curl -w "%{time_total}"`. Also added a fallback JSON parser for
|
||||||
|
when `jq` isn't installed, and an optional CSV log file.
|
||||||
|
|
||||||
|
4. **Dockerfile ran `npm init -y && npm install` on every build**, with no
|
||||||
|
lockfile/manifest — slow, non-reproducible, and re-downloads
|
||||||
|
dependencies on every rebuild even if nothing changed. Added a real
|
||||||
|
`package.json` and split `COPY package.json` from `COPY server.js` so
|
||||||
|
Docker caches the `npm install` layer. Also switched
|
||||||
|
`node:20-alpine3.16` (an older, unsupported Alpine point release) to
|
||||||
|
`node:20-alpine` (current supported tag).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── Dockerfile
|
||||||
|
├── package.json
|
||||||
|
├── server.js
|
||||||
|
├── public/
|
||||||
|
│ └── index.html ← dashboard, served at /
|
||||||
|
└── monitor.sh ← external checker, run it against the deployed URL
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
# open http://localhost:3000 -> dashboard
|
||||||
|
# open http://localhost:3000/health -> raw JSON
|
||||||
|
```
|
||||||
|
|
||||||
|
In another terminal, point the monitor at it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x monitor.sh
|
||||||
|
./monitor.sh http://localhost:3000 30
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build & run with Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t api-health-demo .
|
||||||
|
docker run -d -p 3000:3000 --name api-health-demo api-health-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
## `/health` response shape
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "Online",
|
||||||
|
"uptime_seconds": 42,
|
||||||
|
"request_count": 17,
|
||||||
|
"response_time_ms": 3,
|
||||||
|
"timestamp": "2026-07-27T20:10:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy to ghaymah.systems
|
||||||
|
|
||||||
|
See `DEPLOY_GHAYMAH.md` for the step-by-step (build → push → create
|
||||||
|
service → set port 3000 → point `monitor.sh` at the live URL).
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ar" dir="rtl">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>لوحة مراقبة الـ API</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f4f7f6; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
||||||
.dashboard { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); text-align: center; width: 400px; }
|
|
||||||
h2 { color: #333; margin-bottom: 20px; }
|
|
||||||
.metric { background: #eef2f5; margin: 10px 0; padding: 15px; border-radius: 8px; font-size: 1.2em; display: flex; justify-content: space-between; }
|
|
||||||
.metric span { font-weight: bold; color: #0056b3; }
|
|
||||||
.status-dot { display: inline-block; width: 12px; height: 12px; border-radius: 50%; background: gray; margin-left: 8px; }
|
|
||||||
.online { background: #28a745; }
|
|
||||||
.offline { background: #dc3545; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="dashboard">
|
|
||||||
<h2>مراقبة النظام <span id="statusDot" class="status-dot"></span></h2>
|
|
||||||
|
|
||||||
<div class="metric">
|
|
||||||
الحالة: <span id="statusText">جاري التحميل...</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
زمن الاستجابة: <span id="responseTime">0 ms</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
عدد الطلبات الإجمالي: <span id="requestCount">0</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const API_URL = "";
|
|
||||||
|
|
||||||
async function fetchMetrics() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(API_URL);
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
document.getElementById('statusText').innerText = data.status;
|
|
||||||
document.getElementById('statusText').style.color = '#28a745';
|
|
||||||
document.getElementById('statusDot').className = 'status-dot online';
|
|
||||||
|
|
||||||
document.getElementById('responseTime').innerText = data.response_time_ms + ' ms';
|
|
||||||
document.getElementById('requestCount').innerText = data.request_count;
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('statusText').innerText = 'انقطاع الاتصال (Offline)';
|
|
||||||
document.getElementById('statusText').style.color = '#dc3545';
|
|
||||||
document.getElementById('statusDot').className = 'status-dot offline';
|
|
||||||
document.getElementById('responseTime').innerText = '-';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(fetchMetrics, 5000);
|
|
||||||
fetchMetrics();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
14
Q1/package.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "api-health-monitor-demo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Simple Express API with /health endpoint, monitored and dashboarded, deployed on ghaymah.systems",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.19.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Q1/server.js
@@ -1,35 +1,44 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
|
// --- Simple in-memory metrics (reset when the process restarts) ---------
|
||||||
let requestCount = 0;
|
let requestCount = 0;
|
||||||
let lastResponseTime = 0;
|
let lastResponseTime = 0;
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
requestCount++;
|
requestCount++;
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
res.on('finish', () => {
|
res.on('finish', () => {
|
||||||
lastResponseTime = Date.now() - start;
|
lastResponseTime = Date.now() - start;
|
||||||
});
|
});
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Serve the dashboard (public/index.html) at the root, plus any static assets
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
// --- Health check used by the monitor script and by ghaymah's platform --
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
status: 'Online',
|
status: 'Online',
|
||||||
uptime_seconds: process.uptime(),
|
uptime_seconds: Math.round(process.uptime()),
|
||||||
request_count: requestCount,
|
request_count: requestCount,
|
||||||
response_time_ms: lastResponseTime
|
response_time_ms: lastResponseTime,
|
||||||
});
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
// Plain-text info route, separate from the dashboard which now owns '/'
|
||||||
res.send('Welcome to the Backend API!');
|
app.get('/api', (req, res) => {
|
||||||
|
res.send('Welcome to the Backend API! Health check available at /health');
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, '0.0.0.0', () => {
|
||||||
console.log(`API is running on port ${PORT}`);
|
console.log(`API is running on port ${PORT}`);
|
||||||
});
|
});
|
||||||
113
README.md
@@ -1,2 +1,113 @@
|
|||||||
# Ghaymah-Exam-GamalMohamed-SRE
|
# Ghaymah SRE Project
|
||||||
|
|
||||||
|
This repository contains a full set of SRE and DevOps deliverables for the Ghaymah platform. It includes a health-monitoring web app, a containerized dashboard, an incident postmortem, CI/CD guidance, and a high-traffic architecture design.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
The project is organized into five main parts:
|
||||||
|
|
||||||
|
- Q1: API health monitoring app and dashboard
|
||||||
|
- Q2: Incident postmortem and resilience recommendations
|
||||||
|
- Q3-CICD: CI/CD workflow and deployment strategy
|
||||||
|
- Q4: High-traffic architecture design for 15,000 RPS
|
||||||
|
- Q5: Containerized monitoring service for uptime and SSL checks
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
Ghaymah_Task/
|
||||||
|
├── Q1/
|
||||||
|
│ ├── Dashboard/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── monitor.sh
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── README.md
|
||||||
|
│ └── server.js
|
||||||
|
├── Q2/
|
||||||
|
│ └── readme,md
|
||||||
|
├── Q3-CICD/
|
||||||
|
│ ├── README.md
|
||||||
|
│ └── workflow.yml
|
||||||
|
├── Q4/
|
||||||
|
│ ├── GhaymahAPI.png
|
||||||
|
│ └── README.md
|
||||||
|
├── Q5/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── Entrypoint.sh
|
||||||
|
│ ├── README.md
|
||||||
|
│ ├── index.html
|
||||||
|
│ └── monitor.py
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Each Part Includes
|
||||||
|
|
||||||
|
### Q1 — API Health Monitor and Dashboard
|
||||||
|
This section provides a lightweight web service that exposes a health endpoint and serves a dashboard for monitoring service status, response time, and request count.
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- [Q1/server.js](Q1/server.js)
|
||||||
|
- [Q1/monitor.sh](Q1/monitor.sh)
|
||||||
|
- [Q1/Dashboard/index.html](Q1/Dashboard/index.html)
|
||||||
|
|
||||||
|
### Q2 — Incident Postmortem
|
||||||
|
This section documents a major outage caused by memory exhaustion and proposes mitigation strategies such as autoscaling, pagination, and observability improvements.
|
||||||
|
|
||||||
|
Key file:
|
||||||
|
- [Q2/readme,md](Q2/readme,md)
|
||||||
|
|
||||||
|
### Q3-CICD — Continuous Integration and Deployment
|
||||||
|
This section outlines the CI/CD workflow for automating build, test, and deployment processes for the platform.
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- [Q3-CICD/workflow.yml](Q3-CICD/workflow.yml)
|
||||||
|
- [Q3-CICD/README.md](Q3-CICD/README.md)
|
||||||
|
|
||||||
|
### Q4 — High-Traffic Architecture Design
|
||||||
|
This section explains how to design a scalable architecture for handling 15,000 requests per second, covering load balancing, caching, database replication, and storage planning.
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- [Q4/README.md](Q4/README.md)
|
||||||
|
- [Q4/GhaymahAPI.png](Q4/GhaymahAPI.png)
|
||||||
|
|
||||||
|
### Q5 — Containerized Monitoring Service
|
||||||
|
This section contains a Dockerized monitoring solution that checks uptime, latency, SSL expiry, and dashboard availability using a simple Python script.
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- [Q5/Dockerfile](Q5/Dockerfile)
|
||||||
|
- [Q5/monitor.py](Q5/monitor.py)
|
||||||
|
- [Q5/Entrypoint.sh](Q5/Entrypoint.sh)
|
||||||
|
|
||||||
|
## How to Use This Repository
|
||||||
|
|
||||||
|
1. Review the documentation in each folder.
|
||||||
|
2. Run the applications locally as described in their own README files.
|
||||||
|
3. Use the CI/CD workflow from Q3-CICD for deployment automation.
|
||||||
|
4. Reference Q4 for the large-scale architecture approach.
|
||||||
|
5. Use Q5 as a container-based monitoring example.
|
||||||
|
|
||||||
|
## Technologies Used
|
||||||
|
|
||||||
|
- Node.js / Express
|
||||||
|
- Python
|
||||||
|
- Docker
|
||||||
|
- HTML / CSS / JavaScript
|
||||||
|
- GitHub Actions workflow-based CI/CD
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This project demonstrates a practical SRE approach covering monitoring, incident response, automation, scalability, and container-based operations for a production-style platform.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
**Gamal Mohamed**
|
||||||
|
|
||||||
|
DevSecOps Engineer
|
||||||
|
|
||||||
|
Qabilah Profile
|
||||||
|
|
||||||
|
```
|
||||||
|
https://qabilah.com/profile/gamalmohammed0909/
|
||||||
|
```
|
||||||
ثنائية
docs/Screenshot 2026-07-27 at 23-54-33 Ghaymah Cloud.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 131 KiB |
ثنائية
docs/Screenshot 2026-07-27 at 23-54-52 Site Watch — Status.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 67 KiB |
ثنائية
docs/Screenshot 2026-07-27 at 23-56-18 .png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 9.8 KiB |
ثنائية
docs/Screenshot 2026-07-27 at 23-56-33 .png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 19 KiB |
ثنائية
docs/Screenshot 2026-07-27 at 23-57-37 Ghaymah Cloud.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 236 KiB |
ثنائية
docs/Screenshot 2026-07-27 at 23-57-45 لوحة مراقبة الـ API.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 35 KiB |
ثنائية
docs/brave_screenshot_deploy.ghaymah.systems.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 132 KiB |