Initial Ghaymah tasks setup
هذا الالتزام موجود في:
17
task1-deploy/.ghaymah.json
Normal file
17
task1-deploy/.ghaymah.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "5ba192b0-d1f0-43a6-963d-be133af424b1",
|
||||
"name": "task1-deploy",
|
||||
"projectId": "34340b16-24f2-4db9-89e4-bf94bb372e68",
|
||||
"ports": [
|
||||
{
|
||||
"expose": true,
|
||||
"number": 8080
|
||||
}
|
||||
],
|
||||
"publicAccess": {
|
||||
"enabled": true,
|
||||
"domain": "auto"
|
||||
},
|
||||
"resourceTier": "t1",
|
||||
"dockerFileName": "Dockerfile"
|
||||
}
|
||||
21
task1-deploy/Dockerfile
Normal file
21
task1-deploy/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM python:3.11-slim AS base
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app.py .
|
||||
|
||||
RUN useradd -m appuser
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD python -c "import urllib.request,sys; sys.exit(0) if urllib.request.urlopen('http://localhost:8080/health').getcode()==200 else sys.exit(1)"
|
||||
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "2", "--timeout", "30", "app:app"]
|
||||
85
task1-deploy/app.py
Normal file
85
task1-deploy/app.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import fcntl
|
||||
import os
|
||||
import time
|
||||
from flask import Flask, jsonify
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# السماح للـ dashboard.html بالوصول للـ API من المتصفح
|
||||
CORS(app)
|
||||
|
||||
START_TIME = time.time()
|
||||
|
||||
# عداد الطلبات مشترك بين gunicorn workers
|
||||
COUNTER_FILE = "/tmp/request_count.txt"
|
||||
|
||||
|
||||
def _increment_and_read_count() -> int:
|
||||
fd = os.open(COUNTER_FILE, os.O_RDWR | os.O_CREAT, 0o644)
|
||||
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX)
|
||||
|
||||
data = os.read(fd, 64).decode().strip()
|
||||
count = int(data) if data else 0
|
||||
|
||||
count += 1
|
||||
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
os.truncate(fd, 0)
|
||||
os.write(fd, str(count).encode())
|
||||
|
||||
return count
|
||||
|
||||
finally:
|
||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def _read_count() -> int:
|
||||
if not os.path.exists(COUNTER_FILE):
|
||||
return 0
|
||||
|
||||
with open(COUNTER_FILE, "r") as f:
|
||||
data = f.read().strip()
|
||||
return int(data) if data else 0
|
||||
|
||||
|
||||
@app.before_request
|
||||
def _count_requests():
|
||||
_increment_and_read_count()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def home():
|
||||
return jsonify({
|
||||
"message": "Ghaymah Training API is running",
|
||||
"status": "ok"
|
||||
})
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
uptime_seconds = round(time.time() - START_TIME, 2)
|
||||
|
||||
return jsonify({
|
||||
"status": "healthy",
|
||||
"uptime_seconds": uptime_seconds,
|
||||
"requests_served": _read_count()
|
||||
}), 200
|
||||
|
||||
|
||||
@app.route("/metrics")
|
||||
def metrics():
|
||||
uptime_seconds = round(time.time() - START_TIME, 2)
|
||||
|
||||
return jsonify({
|
||||
"uptime_seconds": uptime_seconds,
|
||||
"requests_served": _read_count(),
|
||||
"start_time": START_TIME
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8080)
|
||||
161
task1-deploy/dashboard.html
Normal file
161
task1-deploy/dashboard.html
Normal file
@@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>لوحة مراقبة التطبيق - Ghaymah</title>
|
||||
<style>
|
||||
:root {
|
||||
--up: #16a34a;
|
||||
--down: #dc2626;
|
||||
--bg: #0f172a;
|
||||
--card: #1e293b;
|
||||
--text: #e2e8f0;
|
||||
--muted: #94a3b8;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: "Segoe UI", Tahoma, sans-serif;
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
}
|
||||
h1 { margin-bottom: 4px; }
|
||||
.sub { color: var(--muted); margin-bottom: 24px; }
|
||||
.controls { margin-bottom: 20px; display: flex; gap: 8px; }
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #334155;
|
||||
background: #0b1220;
|
||||
color: var(--text);
|
||||
}
|
||||
button {
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
.card h3 { margin: 0 0 8px; color: var(--muted); font-size: 14px; font-weight: 500; }
|
||||
.card .value { font-size: 28px; font-weight: 700; }
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.badge.up { background: rgba(22,163,74,.15); color: var(--up); }
|
||||
.badge.down { background: rgba(220,38,38,.15); color: var(--down); }
|
||||
#log {
|
||||
margin-top: 20px;
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.log-line { padding: 4px 0; border-bottom: 1px solid #334155; color: var(--muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>📊 لوحة مراقبة التطبيق</h1>
|
||||
<div class="sub">يتم الفحص تلقائياً كل 5 ثوانٍ عبر /health و /metrics</div>
|
||||
|
||||
<div class="controls">
|
||||
<input id="appUrl" value="https://task1-deploy-b18316770d08.hosted.ghaymah.systems" placeholder="رابط التطبيق مثال: https://myapp.ghaymah.systems">
|
||||
<button onclick="startMonitoring()">ابدأ المراقبة</button>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>الحالة</h3>
|
||||
<div class="value"><span id="statusBadge" class="badge down">غير معروف</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>زمن الاستجابة</h3>
|
||||
<div class="value" id="latency">-- ms</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>عدد الطلبات المخدومة</h3>
|
||||
<div class="value" id="reqCount">--</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>مدة تشغيل التطبيق</h3>
|
||||
<div class="value" id="uptime">--</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="log">
|
||||
<div class="log-line">جاهز... اضغط "ابدأ المراقبة"</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let timer = null;
|
||||
|
||||
function fmtUptime(sec) {
|
||||
const h = Math.floor(sec / 3600);
|
||||
const m = Math.floor((sec % 3600) / 60);
|
||||
const s = Math.floor(sec % 60);
|
||||
return `${h}س ${m}د ${s}ث`;
|
||||
}
|
||||
|
||||
function logLine(text, ok) {
|
||||
const log = document.getElementById('log');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'log-line';
|
||||
div.style.color = ok ? '#16a34a' : '#dc2626';
|
||||
div.textContent = text;
|
||||
log.prepend(div);
|
||||
while (log.children.length > 20) log.removeChild(log.lastChild);
|
||||
}
|
||||
|
||||
async function checkOnce() {
|
||||
const base = document.getElementById('appUrl').value.replace(/\/$/, '');
|
||||
const start = performance.now();
|
||||
try {
|
||||
const res = await fetch(base + '/health', { cache: 'no-store' });
|
||||
const latencyMs = Math.round(performance.now() - start);
|
||||
const data = await res.json();
|
||||
|
||||
document.getElementById('statusBadge').textContent = res.ok ? 'يعمل ✅' : 'متعطل ❌';
|
||||
document.getElementById('statusBadge').className = 'badge ' + (res.ok ? 'up' : 'down');
|
||||
document.getElementById('latency').textContent = latencyMs + ' ms';
|
||||
document.getElementById('reqCount').textContent = data.requests_served ?? '--';
|
||||
document.getElementById('uptime').textContent = data.uptime_seconds ? fmtUptime(data.uptime_seconds) : '--';
|
||||
|
||||
logLine(`${new Date().toLocaleTimeString('ar')} — OK (${latencyMs}ms)`, true);
|
||||
} catch (err) {
|
||||
document.getElementById('statusBadge').textContent = 'متعطل ❌';
|
||||
document.getElementById('statusBadge').className = 'badge down';
|
||||
document.getElementById('latency').textContent = '-- ms';
|
||||
logLine(`${new Date().toLocaleTimeString('ar')} — فشل الاتصال: ${err.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function startMonitoring() {
|
||||
if (timer) clearInterval(timer);
|
||||
checkOnce();
|
||||
timer = setInterval(checkOnce, 5000);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
6
task1-deploy/metrics.json
Normal file
6
task1-deploy/metrics.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"timestamp": "2026-07-28 19:13:13",
|
||||
"status": "UP",
|
||||
"http_code": "200",
|
||||
"response_ms": 1765
|
||||
}
|
||||
3
task1-deploy/monitor.log
Normal file
3
task1-deploy/monitor.log
Normal file
@@ -0,0 +1,3 @@
|
||||
2026-07-28 19:12:12 | status=UP | http_code=200 | response_ms=721
|
||||
2026-07-28 19:12:43 | status=UP | http_code=200 | response_ms=578
|
||||
2026-07-28 19:13:13 | status=UP | http_code=200 | response_ms=1765
|
||||
45
task1-deploy/monitor.sh
Executable file
45
task1-deploy/monitor.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
URL="${APP_URL:-http://localhost:8080/health}"
|
||||
INTERVAL="${CHECK_INTERVAL:-30}"
|
||||
LOG_FILE="${LOG_FILE:-monitor.log}"
|
||||
ALERT_FILE="${ALERT_FILE:-alerts.log}"
|
||||
METRICS_JSON="${METRICS_JSON:-metrics.json}"
|
||||
MAX_TIMEOUT=5
|
||||
|
||||
echo "🔍 بدء المراقبة على: $URL (كل ${INTERVAL}s) — اضغط Ctrl+C للإيقاف"
|
||||
|
||||
while true; do
|
||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
START_NS=$(date +%s%N)
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$MAX_TIMEOUT" "$URL")
|
||||
CURL_EXIT=$?
|
||||
END_NS=$(date +%s%N)
|
||||
RESPONSE_MS=$(( (END_NS - START_NS) / 1000000 ))
|
||||
|
||||
if [ "$CURL_EXIT" -eq 0 ] && [ "$HTTP_CODE" == "200" ]; then
|
||||
STATUS="UP"
|
||||
else
|
||||
STATUS="DOWN"
|
||||
fi
|
||||
|
||||
LINE="$TIMESTAMP | status=$STATUS | http_code=$HTTP_CODE | response_ms=$RESPONSE_MS"
|
||||
echo "$LINE"
|
||||
echo "$LINE" >> "$LOG_FILE"
|
||||
|
||||
|
||||
if [ "$STATUS" == "DOWN" ]; then
|
||||
echo "🚨 $TIMESTAMP ALERT: الخدمة غير متاحة (http_code=$HTTP_CODE)" | tee -a "$ALERT_FILE"
|
||||
fi
|
||||
|
||||
cat > "$METRICS_JSON" <<EOF
|
||||
{
|
||||
"timestamp": "$TIMESTAMP",
|
||||
"status": "$STATUS",
|
||||
"http_code": "$HTTP_CODE",
|
||||
"response_ms": $RESPONSE_MS
|
||||
}
|
||||
EOF
|
||||
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
3
task1-deploy/requirements.txt
Normal file
3
task1-deploy/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
flask==3.0.3
|
||||
gunicorn==22.0.0
|
||||
flask-cors
|
||||
المرجع في مشكلة جديدة
حظر مستخدم