test_infi / app.py
digitalhub483's picture
Create app.py
63aa94a verified
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
import httpx
import os
import threading
app = FastAPI(title="HF Spaces Auto-Ping API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── Shared state ────────────────────────────────────────────────────────────
state = {
"total_requests": 0,
"auto_pings": 0,
"manual_requests": 0,
"last_ping": None,
"ping_log": [], # last 20 ping results
"lock": threading.Lock(),
}
SELF_URL = os.getenv("SPACE_HOST", "http://localhost:7860")
# ── Helpers ─────────────────────────────────────────────────────────────────
def increment(kind: str):
with state["lock"]:
state["total_requests"] += 1
state[kind] += 1
def auto_ping():
"""Called by the scheduler every 60 seconds."""
try:
resp = httpx.get(f"{SELF_URL}/ping", timeout=10)
status = resp.status_code
except Exception as e:
status = f"ERROR: {e}"
ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
entry = {"time": ts, "status": status}
with state["lock"]:
state["ping_log"].insert(0, entry)
state["ping_log"] = state["ping_log"][:20]
state["last_ping"] = ts
# ── Scheduler ────────────────────────────────────────────────────────────────
scheduler = BackgroundScheduler()
scheduler.add_job(auto_ping, "interval", minutes=1, id="auto_ping")
scheduler.start()
# ── Routes ───────────────────────────────────────────────────────────────────
@app.middleware("http")
async def count_requests(request: Request, call_next):
# Don't double-count the /ping endpoint hit by the scheduler
# (it increments itself); count everything else here.
if request.url.path not in ("/ping",):
increment("manual_requests")
response = await call_next(request)
return response
@app.get("/ping")
def ping():
"""Internal health-check endpoint hit by the scheduler."""
increment("auto_pings")
return {"status": "ok", "timestamp": datetime.utcnow().isoformat()}
@app.get("/stats")
def stats():
"""Return live counters as JSON."""
with state["lock"]:
return {
"total_requests": state["total_requests"],
"auto_pings": state["auto_pings"],
"manual_requests": state["manual_requests"],
"last_auto_ping": state["last_ping"],
"ping_log": state["ping_log"],
}
@app.get("/", response_class=HTMLResponse)
def dashboard():
"""Live dashboard – auto-refreshes every 10 s."""
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>HF Spaces · Request Monitor</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:2rem}
h1{font-size:1.8rem;font-weight:700;color:#7dd3fc;margin-bottom:.25rem}
.sub{color:#94a3b8;font-size:.9rem;margin-bottom:2rem}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1.2rem;margin-bottom:2rem}
.card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:1.4rem;text-align:center}
.card .val{font-size:2.6rem;font-weight:800;line-height:1.1}
.card .lbl{font-size:.78rem;text-transform:uppercase;letter-spacing:.08em;color:#94a3b8;margin-top:.4rem}
.total .val{color:#34d399}
.pings .val{color:#60a5fa}
.manual .val{color:#f472b6}
h2{font-size:1.1rem;color:#cbd5e1;margin-bottom:.8rem}
table{width:100%;border-collapse:collapse;background:#1e293b;border-radius:10px;overflow:hidden}
th{background:#0f172a;color:#94a3b8;font-size:.75rem;text-transform:uppercase;letter-spacing:.06em;padding:.6rem 1rem;text-align:left}
td{padding:.55rem 1rem;font-size:.85rem;border-top:1px solid #334155}
.ok{color:#34d399}.err{color:#f87171}
.refresh{font-size:.78rem;color:#475569;margin-top:1.5rem;text-align:right}
#countdown{color:#7dd3fc;font-weight:600}
</style>
</head>
<body>
<h1>🚀 HF Spaces · Request Monitor</h1>
<p class="sub">Auto-pings every 60 s &nbsp;|&nbsp; Dashboard refreshes every 10 s</p>
<div class="grid">
<div class="card total"><div class="val" id="total">—</div><div class="lbl">Total Requests</div></div>
<div class="card pings"><div class="val" id="pings">—</div><div class="lbl">Auto Pings</div></div>
<div class="card manual"><div class="val" id="manual">—</div><div class="lbl">Manual Requests</div></div>
</div>
<h2>📋 Last 20 Auto-Ping Results</h2>
<table>
<thead><tr><th>#</th><th>Timestamp (UTC)</th><th>Status</th></tr></thead>
<tbody id="log"><tr><td colspan="3" style="color:#475569">Loading…</td></tr></tbody>
</table>
<p class="refresh">Next refresh in <span id="countdown">10</span>s</p>
<script>
async function refresh(){
try{
const r = await fetch('/stats');
const d = await r.json();
document.getElementById('total').textContent = d.total_requests;
document.getElementById('pings').textContent = d.auto_pings;
document.getElementById('manual').textContent = d.manual_requests;
const tbody = document.getElementById('log');
if(!d.ping_log.length){
tbody.innerHTML='<tr><td colspan="3" style="color:#475569">No pings yet — first ping in &lt;1 min</td></tr>';
} else {
tbody.innerHTML = d.ping_log.map((e,i)=>`
<tr>
<td style="color:#64748b">${i+1}</td>
<td>${e.time}</td>
<td class="${String(e.status)==='200'?'ok':'err'}">${e.status}</td>
</tr>`).join('');
}
}catch(e){console.error(e)}
}
let secs=10;
refresh();
setInterval(()=>{
secs--;
document.getElementById('countdown').textContent=secs;
if(secs<=0){secs=10;refresh();}
},1000);
</script>
</body>
</html>
"""
return HTMLResponse(content=html)