Remove unnecessary files
هذا الالتزام موجود في:
ثنائية
q1-deploy-monitor/__pycache__/app.cpython-313.pyc
Normal file
ثنائية
q1-deploy-monitor/__pycache__/app.cpython-313.pyc
Normal file
ملف ثنائي غير معروض.
@@ -1,40 +1,48 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, Request
|
||||||
import time
|
import time
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
request_count = 0
|
request_count = 0
|
||||||
|
last_response_time_ms = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
@app.middleware("http")
|
||||||
|
async def track_requests(request: Request, call_next):
|
||||||
|
"""
|
||||||
|
Measures the REAL processing time of every request using a middleware
|
||||||
|
that wraps the actual handler, instead of measuring time inside the
|
||||||
|
handler itself (which always returns ~0ms).
|
||||||
|
We skip counting calls to /stats itself so the dashboard polling
|
||||||
|
doesn't inflate the request counter.
|
||||||
|
"""
|
||||||
|
global request_count, last_response_time_ms
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
response = await call_next(request)
|
||||||
|
duration_ms = round((time.time() - start) * 1000, 2)
|
||||||
|
|
||||||
|
if request.url.path != "/stats":
|
||||||
|
request_count += 1
|
||||||
|
last_response_time_ms = duration_ms
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def home():
|
def home():
|
||||||
global request_count
|
return {"message": "Application is running"}
|
||||||
request_count += 1
|
|
||||||
|
|
||||||
return {
|
|
||||||
"message": "Application is running"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
return {
|
return {"status": "UP"}
|
||||||
"status": "UP"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/stats")
|
@app.get("/stats")
|
||||||
def stats():
|
def stats():
|
||||||
global request_count
|
|
||||||
|
|
||||||
start = time.time()
|
|
||||||
|
|
||||||
request_count += 1
|
|
||||||
|
|
||||||
response_time = round((time.time() - start) * 1000, 2)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "UP",
|
"status": "UP",
|
||||||
"requests": request_count,
|
"requests": request_count,
|
||||||
"response_time": response_time
|
"response_time": last_response_time_ms
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,10 @@
|
|||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
color:green;
|
color:green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.down{
|
||||||
|
color:red;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
@@ -64,25 +68,39 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
// IMPORTANT: change this after deploying to ghaymah.systems
|
||||||
|
// e.g. "https://your-app-name.ghaymah.systems"
|
||||||
|
const API_URL = "http://127.0.0.1:8000";
|
||||||
|
|
||||||
async function updateDashboard() {
|
async function updateDashboard() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const response = await fetch("http://127.0.0.1:8000/stats");
|
// Measure real round-trip latency from the browser's point of view
|
||||||
|
const clientStart = performance.now();
|
||||||
|
|
||||||
|
const response = await fetch(`${API_URL}/stats`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
document.getElementById("status").textContent = data.status;
|
const clientLatency = Math.round(performance.now() - clientStart);
|
||||||
|
|
||||||
|
const statusEl = document.getElementById("status");
|
||||||
|
statusEl.textContent = data.status;
|
||||||
|
statusEl.classList.toggle("down", data.status !== "UP");
|
||||||
|
|
||||||
document.getElementById("requests").textContent = data.requests;
|
document.getElementById("requests").textContent = data.requests;
|
||||||
|
|
||||||
document.getElementById("response").textContent = data.response_time;
|
// Show the server-measured latency; fall back to client-measured
|
||||||
|
// round-trip time if the server didn't report anything useful.
|
||||||
|
document.getElementById("response").textContent =
|
||||||
|
data.response_time > 0 ? data.response_time : clientLatency;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch(error){
|
catch(error){
|
||||||
|
|
||||||
document.getElementById("status").textContent = "DOWN";
|
const statusEl = document.getElementById("status");
|
||||||
|
statusEl.textContent = "DOWN";
|
||||||
|
statusEl.classList.add("down");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +108,7 @@ async function updateDashboard() {
|
|||||||
|
|
||||||
updateDashboard();
|
updateDashboard();
|
||||||
|
|
||||||
setInterval(updateDashboard,30000);
|
setInterval(updateDashboard, 30000);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,33 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker push registry.ghaymah.systems/my-api:${{ github.sha }}
|
docker push registry.ghaymah.systems/my-api:${{ github.sha }}
|
||||||
|
|
||||||
deploy-production:
|
deploy-staging:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
environment:
|
||||||
|
name: staging
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Deploy to Staging
|
||||||
|
run: |
|
||||||
|
echo "Deploying image ${{ github.sha }} to staging..."
|
||||||
|
# ghaymah deploy my-api-staging --image registry.ghaymah.systems/my-api:${{ github.sha }}
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
needs: deploy-staging
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# This "environment: production" line by itself does NOT block the job.
|
||||||
|
# The manual approval only happens if you also configure, on GitHub:
|
||||||
|
# Settings -> Environments -> production -> Required reviewers
|
||||||
|
# Add yourself (or a teammate) as a required reviewer there.
|
||||||
|
# Without that step, this environment tag is just a label.
|
||||||
environment:
|
environment:
|
||||||
name: production
|
name: production
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to Production
|
- name: Deploy to Production
|
||||||
run: |
|
run: |
|
||||||
echo "Deploying application to production..."
|
echo "Deploying image ${{ github.sha }} to production..."
|
||||||
|
# ghaymah deploy my-api --image registry.ghaymah.systems/my-api:${{ github.sha }}
|
||||||
|
|||||||
ملف ثنائي غير معروض.
|
قبل العرض: | الارتفاع: | الحجم: 44 KiB |
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 . .
|
||||||
|
RUN chmod +x start.sh
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["./start.sh"]
|
||||||
11
q5-mithal-monitor/Start.sh
Normal file
11
q5-mithal-monitor/Start.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash\
|
||||||
|
# Runs the background monitoring loop (monitor.py) and serves the
|
||||||
|
# dashboard + monitor_data.json over HTTP at the same time.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Start the monitor in the background; it updates monitor_data.json every 60s
|
||||||
|
python monitor.py &
|
||||||
|
|
||||||
|
# Serve the current directory (dashboard.html + monitor_data.json)
|
||||||
|
python -m http.server 8080
|
||||||
تم حذف اختلاف الملف لأن الملف كبير جداً
تحميل الاختلاف
1
q5-mithal-monitor/requirements.txt
Normal file
1
q5-mithal-monitor/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
requests
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم