Restructure repository for final submission
هذا الالتزام موجود في:
20
q1-deploy-monitor/Dockerfile
Normal file
20
q1-deploy-monitor/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies first (layer caching)
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy app code
|
||||
COPY app.py .
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
# Basic container-level health check (Docker/most platforms respect this)
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')" || exit 1
|
||||
|
||||
# gunicorn for production-grade serving instead of Flask dev server
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]
|
||||
|
||||
179
q1-deploy-monitor/README.md
Normal file
179
q1-deploy-monitor/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Q1 - Deploy and Monitor an API on Ghaymah Cloud
|
||||
|
||||
## Overview
|
||||
|
||||
This project demonstrates deploying a Dockerized Python API to **Ghaymah Cloud** and implementing a simple monitoring solution.
|
||||
|
||||
The application exposes a `/health` endpoint that is continuously monitored using a Python script. A lightweight HTML dashboard displays the application's health status, response time, uptime, and request statistics.
|
||||
|
||||
---
|
||||
|
||||
# Project Structure
|
||||
|
||||
```
|
||||
Q1-Deploy-and-Monitoring/
|
||||
│
|
||||
├── screenshots/
|
||||
│ ├── dashboard-local.png
|
||||
│ └── dashboard-ghaymah.png
|
||||
│
|
||||
├── Dockerfile
|
||||
├── app.py
|
||||
├── health-check.py
|
||||
├── dashboard.html
|
||||
├── monitor-log.csv
|
||||
├── requirements.txt
|
||||
├── install-docker.sh
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Task Requirements
|
||||
|
||||
This implementation satisfies all requirements of Question 1.
|
||||
|
||||
| Requirement | Status |
|
||||
|------------|--------|
|
||||
| Dockerize the API | ✅ |
|
||||
| Deploy to Ghaymah Cloud | ✅ |
|
||||
| Implement `/health` endpoint | ✅ |
|
||||
| Monitoring script (every 30 seconds) | ✅ |
|
||||
| Monitoring Dashboard | ✅ |
|
||||
|
||||
---
|
||||
|
||||
# Technologies
|
||||
|
||||
- Python
|
||||
- Flask
|
||||
- Docker
|
||||
- HTML
|
||||
- CSS
|
||||
- JavaScript
|
||||
- Ghaymah Cloud
|
||||
- Ghaymah CLI
|
||||
|
||||
---
|
||||
|
||||
# API
|
||||
|
||||
## Health Endpoint
|
||||
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
Example response
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy"
|
||||
}
|
||||
```
|
||||
|
||||
The monitoring script periodically sends requests to this endpoint to verify application availability.
|
||||
|
||||
---
|
||||
|
||||
# Docker
|
||||
|
||||
Build the Docker image
|
||||
|
||||
```bash
|
||||
docker build -t exam-api .
|
||||
```
|
||||
|
||||
Run the container
|
||||
|
||||
```bash
|
||||
docker run -d -p 5000:5000 exam-api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Monitoring Script
|
||||
|
||||
The monitoring script (`health-check.py`) executes every **30 seconds** and performs the following operations:
|
||||
|
||||
- Sends an HTTP request to `/health`
|
||||
- Measures response latency
|
||||
- Detects application availability
|
||||
- Records monitoring results
|
||||
- Updates `monitor-log.csv`
|
||||
|
||||
Run the monitor
|
||||
|
||||
```bash
|
||||
python3 health-check.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Monitoring Dashboard
|
||||
|
||||
The dashboard was implemented using HTML, CSS and JavaScript.
|
||||
|
||||
Displayed metrics include:
|
||||
|
||||
- Current application status
|
||||
- Response time
|
||||
- Total requests
|
||||
- Application uptime
|
||||
- Monitoring history
|
||||
|
||||
---
|
||||
|
||||
# Deployment using Ghaymah CLI
|
||||
|
||||
The application was deployed to **Ghaymah Cloud** using the official CLI.
|
||||
|
||||
Deployment workflow:
|
||||
|
||||
1. Install Ghaymah CLI
|
||||
2. Authenticate using account credentials
|
||||
3. Select deployment configuration
|
||||
4. Launch the application
|
||||
|
||||
Example commands
|
||||
|
||||
```bash
|
||||
gy auth login
|
||||
|
||||
cp .ghaymah.production.json .ghaymah.json
|
||||
|
||||
gy resource app launch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Dashboard Preview
|
||||
|
||||
## Local Testing
|
||||
|
||||
The dashboard was first tested locally against the application running on the EC2 instance before deploying to Ghaymah Cloud.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Ghaymah Cloud Deployment
|
||||
|
||||
After deployment, the dashboard successfully monitored the live application hosted on **Ghaymah Cloud**.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Result
|
||||
|
||||
The project successfully demonstrates:
|
||||
|
||||
- Docker containerization
|
||||
- Cloud deployment on Ghaymah
|
||||
- Automated health monitoring
|
||||
- Response time tracking
|
||||
- Monitoring dashboard
|
||||
- Continuous application health verification
|
||||
|
||||
This implementation satisfies all requirements of **Question 1 – Deploy and Monitor an API on Ghaymah Cloud**.
|
||||
60
q1-deploy-monitor/app.py
Normal file
60
q1-deploy-monitor/app.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Simple API app for Ghaymah SRE exam - Q1
|
||||
Provides:
|
||||
GET / -> basic info
|
||||
GET /health -> health check endpoint (used by monitoring)
|
||||
GET /metrics -> simple JSON metrics (request count, uptime)
|
||||
"""
|
||||
import time
|
||||
from flask import Flask, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.after_request
|
||||
def add_cors_headers(response):
|
||||
# Allows the dashboard (served from a different origin, e.g. file://
|
||||
# or another host) to call this API from the browser.
|
||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
|
||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
|
||||
return response
|
||||
|
||||
START_TIME = time.time()
|
||||
REQUEST_COUNT = 0
|
||||
|
||||
|
||||
@app.before_request
|
||||
def count_requests():
|
||||
global REQUEST_COUNT
|
||||
REQUEST_COUNT += 1
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return jsonify({
|
||||
"service": "ghaymah-exam-api",
|
||||
"message": "API is running"
|
||||
})
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
"""Used by ghaymah.systems platform + our monitoring script."""
|
||||
return jsonify({
|
||||
"status": "healthy",
|
||||
"uptime_seconds": round(time.time() - START_TIME, 2)
|
||||
}), 200
|
||||
|
||||
|
||||
@app.route("/metrics")
|
||||
def metrics():
|
||||
return jsonify({
|
||||
"uptime_seconds": round(time.time() - START_TIME, 2),
|
||||
"total_requests": REQUEST_COUNT
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 0.0.0.0 required so the container's port is reachable externally
|
||||
app.run(host="0.0.0.0", port=5000)
|
||||
211
q1-deploy-monitor/dashboard.html
Normal file
211
q1-deploy-monitor/dashboard.html
Normal file
@@ -0,0 +1,211 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Monitoring Dashboard - Ghaymah Exam Q1</title>
|
||||
<style>
|
||||
:root {
|
||||
--up: #22c55e;
|
||||
--down: #ef4444;
|
||||
--bg: #0f172a;
|
||||
--card: #1e293b;
|
||||
--text: #e2e8f0;
|
||||
--muted: #94a3b8;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', Tahoma, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
padding: 24px;
|
||||
}
|
||||
h1 { font-size: 22px; margin-bottom: 4px; }
|
||||
.subtitle { color: var(--muted); margin-bottom: 24px; font-size: 14px; }
|
||||
|
||||
.config {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.config input {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #334155;
|
||||
background: var(--card);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
}
|
||||
.config button {
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.config button:hover { background: #2563eb; }
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
.card .label {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.card .value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.status-up { color: var(--up); }
|
||||
.status-down { color: var(--down); }
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.dot-up { background: var(--up); box-shadow: 0 0 8px var(--up); }
|
||||
.dot-down { background: var(--down); box-shadow: 0 0 8px var(--down); }
|
||||
|
||||
.log {
|
||||
background: var(--card);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #334155;
|
||||
padding: 16px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.log table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.log th, .log td { text-align: right; padding: 6px 8px; border-bottom: 1px solid #334155; }
|
||||
.log th { color: var(--muted); font-weight: 500; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>لوحة مراقبة التطبيق</h1>
|
||||
<div class="subtitle">Ghaymah SRE Exam — Q1 Monitoring Dashboard</div>
|
||||
|
||||
<div class="config">
|
||||
<input id="appUrl" type="text" placeholder="ضع رابط التطبيق المنشور على ghaymah.systems (مثال: https://myapp.ghaymah.systems)">
|
||||
<button onclick="startMonitoring()">ابدأ المراقبة</button>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="label">الحالة (Status)</div>
|
||||
<div class="value" id="statusValue">—</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">زمن الاستجابة (Response Time)</div>
|
||||
<div class="value" id="latencyValue">— ms</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">عدد الطلبات (Total Requests)</div>
|
||||
<div class="value" id="requestsValue">—</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">وقت التشغيل (Uptime)</div>
|
||||
<div class="value" id="uptimeValue">—</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="log">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>الوقت</th><th>الحالة</th><th>زمن الاستجابة</th></tr>
|
||||
</thead>
|
||||
<tbody id="logBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let intervalId = null;
|
||||
const MAX_LOG_ROWS = 15;
|
||||
|
||||
function startMonitoring() {
|
||||
const url = document.getElementById('appUrl').value.trim();
|
||||
if (!url) { alert('من فضلك ضع رابط التطبيق'); return; }
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
checkNow(url);
|
||||
intervalId = setInterval(() => checkNow(url), 30000);
|
||||
}
|
||||
|
||||
async function checkNow(baseUrl) {
|
||||
const cleanUrl = baseUrl.replace(/\/$/, '');
|
||||
const start = performance.now();
|
||||
let status = 'DOWN';
|
||||
let statusCode = null;
|
||||
|
||||
try {
|
||||
const res = await fetch(cleanUrl + '/health', { cache: 'no-store' });
|
||||
statusCode = res.status;
|
||||
status = res.ok ? 'UP' : 'DEGRADED';
|
||||
} catch (e) {
|
||||
status = 'DOWN';
|
||||
}
|
||||
const latency = Math.round(performance.now() - start);
|
||||
|
||||
updateStatusCard(status, latency);
|
||||
addLogRow(status, latency);
|
||||
|
||||
// metrics endpoint (requests count + uptime) - best effort
|
||||
try {
|
||||
const mRes = await fetch(cleanUrl + '/metrics', { cache: 'no-store' });
|
||||
if (mRes.ok) {
|
||||
const data = await mRes.json();
|
||||
document.getElementById('requestsValue').textContent = data.total_requests ?? '—';
|
||||
document.getElementById('uptimeValue').textContent = formatUptime(data.uptime_seconds);
|
||||
}
|
||||
} catch (e) { /* metrics optional */ }
|
||||
}
|
||||
|
||||
function updateStatusCard(status, latency) {
|
||||
const statusEl = document.getElementById('statusValue');
|
||||
statusEl.textContent = status;
|
||||
statusEl.className = 'value ' + (status === 'UP' ? 'status-up' : 'status-down');
|
||||
|
||||
document.getElementById('latencyValue').textContent = latency + ' ms';
|
||||
}
|
||||
|
||||
function addLogRow(status, latency) {
|
||||
const tbody = document.getElementById('logBody');
|
||||
const row = document.createElement('tr');
|
||||
const dotClass = status === 'UP' ? 'dot-up' : 'dot-down';
|
||||
row.innerHTML = `
|
||||
<td>${new Date().toLocaleTimeString('ar-EG')}</td>
|
||||
<td><span class="dot ${dotClass}"></span>${status}</td>
|
||||
<td>${latency} ms</td>
|
||||
`;
|
||||
tbody.prepend(row);
|
||||
while (tbody.rows.length > MAX_LOG_ROWS) {
|
||||
tbody.deleteRow(tbody.rows.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function formatUptime(seconds) {
|
||||
if (seconds == null) return '—';
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
return `${h}h ${m}m`;
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!-- staging test -->
|
||||
79
q1-deploy-monitor/health-check.py
Normal file
79
q1-deploy-monitor/health-check.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Monitoring script for Q1 - Ghaymah SRE exam.
|
||||
Checks the deployed app's /health endpoint every 30 seconds,
|
||||
logs status + response time to a CSV file, and prints live status
|
||||
to the console.
|
||||
|
||||
Usage:
|
||||
python3 health-check.py https://your-app-url.ghaymah.systems
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
import csv
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
CHECK_INTERVAL_SECONDS = 30
|
||||
LOG_FILE = "monitor-log.csv"
|
||||
|
||||
|
||||
def check_health(url: str) -> dict:
|
||||
endpoint = url.rstrip("/") + "/health"
|
||||
start = time.time()
|
||||
try:
|
||||
with urllib.request.urlopen(endpoint, timeout=10) as response:
|
||||
elapsed_ms = round((time.time() - start) * 1000, 2)
|
||||
status_code = response.getcode()
|
||||
return {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"status": "UP" if status_code == 200 else "DEGRADED",
|
||||
"status_code": status_code,
|
||||
"response_time_ms": elapsed_ms,
|
||||
}
|
||||
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as e:
|
||||
elapsed_ms = round((time.time() - start) * 1000, 2)
|
||||
return {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"status": "DOWN",
|
||||
"status_code": None,
|
||||
"response_time_ms": elapsed_ms,
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
|
||||
def log_result(result: dict):
|
||||
file_exists = os.path.isfile(LOG_FILE)
|
||||
with open(LOG_FILE, "a", newline="") as f:
|
||||
fieldnames = ["timestamp", "status", "status_code", "response_time_ms", "error"]
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
if not file_exists:
|
||||
writer.writeheader()
|
||||
writer.writerow({**{"error": ""}, **result})
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python3 health-check.py <app_url>")
|
||||
sys.exit(1)
|
||||
|
||||
url = sys.argv[1]
|
||||
print(f"Monitoring {url}/health every {CHECK_INTERVAL_SECONDS}s. Logging to {LOG_FILE}. Ctrl+C to stop.")
|
||||
|
||||
try:
|
||||
while True:
|
||||
result = check_health(url)
|
||||
log_result(result)
|
||||
print(f"[{result['timestamp']}] {result['status']} "
|
||||
f"({result['response_time_ms']}ms)"
|
||||
+ (f" - {result.get('error')}" if result.get("error") else ""))
|
||||
time.sleep(CHECK_INTERVAL_SECONDS)
|
||||
except KeyboardInterrupt:
|
||||
print("\nMonitoring stopped.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
36
q1-deploy-monitor/install-docker.sh
Executable file
36
q1-deploy-monitor/install-docker.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo ">>> Updating packages..."
|
||||
sudo apt update
|
||||
|
||||
echo ">>> Installing prerequisites..."
|
||||
sudo apt install -y ca-certificates curl gnupg
|
||||
|
||||
echo ">>> Setting up Docker keyring..."
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
echo ">>> Adding Docker repository..."
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
echo ">>> Installing Docker..."
|
||||
sudo apt update
|
||||
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
echo ">>> Starting Docker service..."
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
|
||||
echo ">>> Adding current user to docker group..."
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Docker installed successfully."
|
||||
echo "IMPORTANT: run 'newgrp docker' OR log out/in"
|
||||
echo "then run: docker --version"
|
||||
echo "=========================================="
|
||||
35
q1-deploy-monitor/monitor-log.csv
Normal file
35
q1-deploy-monitor/monitor-log.csv
Normal file
@@ -0,0 +1,35 @@
|
||||
timestamp,status,status_code,response_time_ms,error
|
||||
2026-07-27T10:17:38.379006+00:00,UP,200,14.79,
|
||||
2026-07-27T10:18:08.381609+00:00,UP,200,2.08,
|
||||
2026-07-27T10:18:38.383866+00:00,UP,200,1.82,
|
||||
2026-07-27T10:19:08.386140+00:00,UP,200,1.77,
|
||||
2026-07-27T10:19:38.388582+00:00,UP,200,2.01,
|
||||
2026-07-27T10:20:08.390836+00:00,UP,200,1.85,
|
||||
2026-07-27T10:20:38.394181+00:00,UP,200,2.97,
|
||||
2026-07-27T10:21:08.396674+00:00,UP,200,1.98,
|
||||
2026-07-27T10:21:38.398784+00:00,UP,200,1.7,
|
||||
2026-07-27T10:22:08.401664+00:00,UP,200,1.84,
|
||||
2026-07-27T10:22:38.403790+00:00,UP,200,1.71,
|
||||
2026-07-27T10:23:08.406096+00:00,UP,200,1.78,
|
||||
2026-07-27T10:23:38.407503+00:00,DOWN,,0.43,<urlopen error [Errno 111] Connection refused>
|
||||
2026-07-27T10:24:08.408384+00:00,DOWN,,0.54,<urlopen error [Errno 111] Connection refused>
|
||||
2026-07-27T10:24:38.409097+00:00,DOWN,,0.38,<urlopen error [Errno 111] Connection refused>
|
||||
2026-07-27T10:25:08.409873+00:00,DOWN,,0.4,<urlopen error [Errno 111] Connection refused>
|
||||
2026-07-27T10:25:38.412335+00:00,UP,200,2.12,
|
||||
2026-07-27T10:26:08.414479+00:00,UP,200,1.78,
|
||||
2026-07-27T10:26:38.416810+00:00,UP,200,1.92,
|
||||
2026-07-27T10:27:08.418967+00:00,UP,200,1.74,
|
||||
2026-07-27T10:27:38.421130+00:00,UP,200,1.68,
|
||||
2026-07-27T10:28:08.423401+00:00,UP,200,1.76,
|
||||
2026-07-27T10:28:38.425814+00:00,UP,200,1.99,
|
||||
2026-07-27T10:29:08.428129+00:00,UP,200,1.76,
|
||||
2026-07-27T10:29:38.430424+00:00,UP,200,1.85,
|
||||
2026-07-27T10:30:08.432756+00:00,UP,200,1.87,
|
||||
2026-07-27T10:30:38.435351+00:00,UP,200,1.77,
|
||||
2026-07-27T10:31:08.437531+00:00,UP,200,1.78,
|
||||
2026-07-27T10:31:38.439587+00:00,UP,200,1.69,
|
||||
2026-07-27T10:32:08.441908+00:00,UP,200,1.94,
|
||||
2026-07-27T10:32:38.443987+00:00,UP,200,1.68,
|
||||
2026-07-27T10:33:08.446193+00:00,UP,200,1.75,
|
||||
2026-07-27T10:33:38.448817+00:00,UP,200,1.85,
|
||||
2026-07-27T10:34:08.451690+00:00,UP,200,1.73,
|
||||
|
3
q1-deploy-monitor/requirements.txt
Normal file
3
q1-deploy-monitor/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
flask==3.0.3
|
||||
gunicorn==22.0.0
|
||||
|
||||
ثنائية
q1-deploy-monitor/screenshots/dashboard-ghaymah.png
Normal file
ثنائية
q1-deploy-monitor/screenshots/dashboard-ghaymah.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 135 KiB |
ثنائية
q1-deploy-monitor/screenshots/dashboard-local.png
Normal file
ثنائية
q1-deploy-monitor/screenshots/dashboard-local.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 134 KiB |
المرجع في مشكلة جديدة
حظر مستخدم