Spaces:
Build error
Build error
| 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 ─────────────────────────────────────────────────────────────────── | |
| 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 | |
| def ping(): | |
| """Internal health-check endpoint hit by the scheduler.""" | |
| increment("auto_pings") | |
| return {"status": "ok", "timestamp": datetime.utcnow().isoformat()} | |
| 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"], | |
| } | |
| 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 | 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 <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) |