digitalhub483 commited on
Commit
63aa94a
·
verified ·
1 Parent(s): 56f6e93

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -0
app.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.responses import HTMLResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from apscheduler.schedulers.background import BackgroundScheduler
5
+ from datetime import datetime
6
+ import httpx
7
+ import os
8
+ import threading
9
+
10
+ app = FastAPI(title="HF Spaces Auto-Ping API")
11
+
12
+ app.add_middleware(
13
+ CORSMiddleware,
14
+ allow_origins=["*"],
15
+ allow_methods=["*"],
16
+ allow_headers=["*"],
17
+ )
18
+
19
+ # ── Shared state ────────────────────────────────────────────────────────────
20
+ state = {
21
+ "total_requests": 0,
22
+ "auto_pings": 0,
23
+ "manual_requests": 0,
24
+ "last_ping": None,
25
+ "ping_log": [], # last 20 ping results
26
+ "lock": threading.Lock(),
27
+ }
28
+
29
+ SELF_URL = os.getenv("SPACE_HOST", "http://localhost:7860")
30
+
31
+
32
+ # ── Helpers ─────────────────────────────────────────────────────────────────
33
+ def increment(kind: str):
34
+ with state["lock"]:
35
+ state["total_requests"] += 1
36
+ state[kind] += 1
37
+
38
+
39
+ def auto_ping():
40
+ """Called by the scheduler every 60 seconds."""
41
+ try:
42
+ resp = httpx.get(f"{SELF_URL}/ping", timeout=10)
43
+ status = resp.status_code
44
+ except Exception as e:
45
+ status = f"ERROR: {e}"
46
+
47
+ ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
48
+ entry = {"time": ts, "status": status}
49
+
50
+ with state["lock"]:
51
+ state["ping_log"].insert(0, entry)
52
+ state["ping_log"] = state["ping_log"][:20]
53
+ state["last_ping"] = ts
54
+
55
+
56
+ # ── Scheduler ────────────────────────────────────────────────────────────────
57
+ scheduler = BackgroundScheduler()
58
+ scheduler.add_job(auto_ping, "interval", minutes=1, id="auto_ping")
59
+ scheduler.start()
60
+
61
+
62
+ # ── Routes ───────────────────────────────────────────────────────────────────
63
+ @app.middleware("http")
64
+ async def count_requests(request: Request, call_next):
65
+ # Don't double-count the /ping endpoint hit by the scheduler
66
+ # (it increments itself); count everything else here.
67
+ if request.url.path not in ("/ping",):
68
+ increment("manual_requests")
69
+ response = await call_next(request)
70
+ return response
71
+
72
+
73
+ @app.get("/ping")
74
+ def ping():
75
+ """Internal health-check endpoint hit by the scheduler."""
76
+ increment("auto_pings")
77
+ return {"status": "ok", "timestamp": datetime.utcnow().isoformat()}
78
+
79
+
80
+ @app.get("/stats")
81
+ def stats():
82
+ """Return live counters as JSON."""
83
+ with state["lock"]:
84
+ return {
85
+ "total_requests": state["total_requests"],
86
+ "auto_pings": state["auto_pings"],
87
+ "manual_requests": state["manual_requests"],
88
+ "last_auto_ping": state["last_ping"],
89
+ "ping_log": state["ping_log"],
90
+ }
91
+
92
+
93
+ @app.get("/", response_class=HTMLResponse)
94
+ def dashboard():
95
+ """Live dashboard – auto-refreshes every 10 s."""
96
+ html = """
97
+ <!DOCTYPE html>
98
+ <html lang="en">
99
+ <head>
100
+ <meta charset="UTF-8"/>
101
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
102
+ <title>HF Spaces · Request Monitor</title>
103
+ <style>
104
+ *{box-sizing:border-box;margin:0;padding:0}
105
+ body{font-family:'Segoe UI',sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:2rem}
106
+ h1{font-size:1.8rem;font-weight:700;color:#7dd3fc;margin-bottom:.25rem}
107
+ .sub{color:#94a3b8;font-size:.9rem;margin-bottom:2rem}
108
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1.2rem;margin-bottom:2rem}
109
+ .card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:1.4rem;text-align:center}
110
+ .card .val{font-size:2.6rem;font-weight:800;line-height:1.1}
111
+ .card .lbl{font-size:.78rem;text-transform:uppercase;letter-spacing:.08em;color:#94a3b8;margin-top:.4rem}
112
+ .total .val{color:#34d399}
113
+ .pings .val{color:#60a5fa}
114
+ .manual .val{color:#f472b6}
115
+ h2{font-size:1.1rem;color:#cbd5e1;margin-bottom:.8rem}
116
+ table{width:100%;border-collapse:collapse;background:#1e293b;border-radius:10px;overflow:hidden}
117
+ th{background:#0f172a;color:#94a3b8;font-size:.75rem;text-transform:uppercase;letter-spacing:.06em;padding:.6rem 1rem;text-align:left}
118
+ td{padding:.55rem 1rem;font-size:.85rem;border-top:1px solid #334155}
119
+ .ok{color:#34d399}.err{color:#f87171}
120
+ .refresh{font-size:.78rem;color:#475569;margin-top:1.5rem;text-align:right}
121
+ #countdown{color:#7dd3fc;font-weight:600}
122
+ </style>
123
+ </head>
124
+ <body>
125
+ <h1>🚀 HF Spaces · Request Monitor</h1>
126
+ <p class="sub">Auto-pings every 60 s &nbsp;|&nbsp; Dashboard refreshes every 10 s</p>
127
+
128
+ <div class="grid">
129
+ <div class="card total"><div class="val" id="total">—</div><div class="lbl">Total Requests</div></div>
130
+ <div class="card pings"><div class="val" id="pings">—</div><div class="lbl">Auto Pings</div></div>
131
+ <div class="card manual"><div class="val" id="manual">—</div><div class="lbl">Manual Requests</div></div>
132
+ </div>
133
+
134
+ <h2>📋 Last 20 Auto-Ping Results</h2>
135
+ <table>
136
+ <thead><tr><th>#</th><th>Timestamp (UTC)</th><th>Status</th></tr></thead>
137
+ <tbody id="log"><tr><td colspan="3" style="color:#475569">Loading…</td></tr></tbody>
138
+ </table>
139
+
140
+ <p class="refresh">Next refresh in <span id="countdown">10</span>s</p>
141
+
142
+ <script>
143
+ async function refresh(){
144
+ try{
145
+ const r = await fetch('/stats');
146
+ const d = await r.json();
147
+ document.getElementById('total').textContent = d.total_requests;
148
+ document.getElementById('pings').textContent = d.auto_pings;
149
+ document.getElementById('manual').textContent = d.manual_requests;
150
+ const tbody = document.getElementById('log');
151
+ if(!d.ping_log.length){
152
+ tbody.innerHTML='<tr><td colspan="3" style="color:#475569">No pings yet — first ping in &lt;1 min</td></tr>';
153
+ } else {
154
+ tbody.innerHTML = d.ping_log.map((e,i)=>`
155
+ <tr>
156
+ <td style="color:#64748b">${i+1}</td>
157
+ <td>${e.time}</td>
158
+ <td class="${String(e.status)==='200'?'ok':'err'}">${e.status}</td>
159
+ </tr>`).join('');
160
+ }
161
+ }catch(e){console.error(e)}
162
+ }
163
+
164
+ let secs=10;
165
+ refresh();
166
+ setInterval(()=>{
167
+ secs--;
168
+ document.getElementById('countdown').textContent=secs;
169
+ if(secs<=0){secs=10;refresh();}
170
+ },1000);
171
+ </script>
172
+ </body>
173
+ </html>
174
+ """
175
+ return HTMLResponse(content=html)