Update src/api/admin.py
Browse files- src/api/admin.py +62 -43
src/api/admin.py
CHANGED
|
@@ -259,10 +259,17 @@ tr:last-child td{{border-bottom:none}}
|
|
| 259 |
</div>
|
| 260 |
|
| 261 |
<div class="card">
|
| 262 |
-
<h2>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
<div id="recentTable" style="overflow-x:auto"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
|
| 264 |
</div>
|
| 265 |
|
|
|
|
| 266 |
<div class="card">
|
| 267 |
<h2>🔌 System Status</h2>
|
| 268 |
<div id="sysStatus"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
|
|
@@ -270,18 +277,62 @@ tr:last-child td{{border-bottom:none}}
|
|
| 270 |
|
| 271 |
</div>
|
| 272 |
<script>
|
|
|
|
|
|
|
| 273 |
async function loadStats(){{
|
| 274 |
try{{
|
| 275 |
const r=await fetch('/admin/metrics');
|
| 276 |
const d=await r.json();
|
| 277 |
document.getElementById('statsGrid').innerHTML=`
|
| 278 |
-
<div class="stat"><div class="val">${{d.agent_requests||0}}</div><div class="lbl">
|
| 279 |
-
<div class="stat"><div class="val">${{d.search_requests||0}}</div><div class="lbl">
|
| 280 |
-
<div class="stat"><div class="val">${{d.follow_up_hits||0}}</div><div class="lbl">Follow-
|
| 281 |
-
<div class="stat"><div class="val">${{d.
|
| 282 |
-
<div class="stat"><div class="val">${{d.feedback_total||0}}</div><div class="lbl">Feedback</div></div>
|
| 283 |
`;
|
| 284 |
-
}}catch(e){{document.getElementById('statsGrid').innerHTML='<span style="color:#dc2626">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
}}
|
| 286 |
|
| 287 |
async function loadMetrics(){{
|
|
@@ -296,7 +347,7 @@ async function loadMetrics(){{
|
|
| 296 |
.map(([k,v])=>`<tr><td style="color:#dc2626">${{k}}</td><td>${{v}}</td><td style="color:#6b7280">error</td></tr>`).join('');
|
| 297 |
document.getElementById('metricsTable').innerHTML=rows
|
| 298 |
?`<table><thead><tr><th>Key</th><th>Count</th><th>Type</th></tr></thead><tbody>${{rows}}</tbody></table>`
|
| 299 |
-
:'<span style="color:#6b7280;font-size:.84rem">No data yet.</span>';
|
| 300 |
}}catch(e){{document.getElementById('metricsTable').innerHTML='<span style="color:#dc2626">Could not load metrics</span>';}}
|
| 301 |
}}
|
| 302 |
|
|
@@ -351,36 +402,14 @@ async function loadStatus(){{
|
|
| 351 |
}}
|
| 352 |
}}
|
| 353 |
|
| 354 |
-
async function loadRecentQueries(){{
|
| 355 |
-
try{{
|
| 356 |
-
const r=await fetch('/admin/queries?limit=50');
|
| 357 |
-
const d=await r.json();
|
| 358 |
-
const rows=(d.queries||[]).map(q=>`<tr>
|
| 359 |
-
<td style="white-space:nowrap">${{q.timestamp||''}}</td>
|
| 360 |
-
<td style="max-width:260px;word-break:break-word">${{(q.question||'').substring(0,100)}}</td>
|
| 361 |
-
<td>${{q.intent||''}}</td>
|
| 362 |
-
<td>${{q.tool||''}}</td>
|
| 363 |
-
<td>${{q.model||''}}</td>
|
| 364 |
-
<td style="white-space:nowrap">${{(q.response_time||0).toFixed(2)}}s</td>
|
| 365 |
-
<td>${{q.result_count||0}}</td>
|
| 366 |
-
</tr>`).join('');
|
| 367 |
-
document.getElementById('recentTable').innerHTML=rows
|
| 368 |
-
?`<table><thead><tr>
|
| 369 |
-
<th>Time</th><th>Question</th><th>Intent</th>
|
| 370 |
-
<th>Tool</th><th>Model</th><th>Time</th><th>Results</th>
|
| 371 |
-
</tr></thead><tbody>${{rows}}</tbody></table>`
|
| 372 |
-
:'<span style="color:#6b7280;font-size:.84rem">No queries logged yet — queries appear after users interact with LibBee.</span>';
|
| 373 |
-
}}catch(e){{
|
| 374 |
-
document.getElementById('recentTable').innerHTML='<span style="color:#dc2626">Could not load recent queries</span>';
|
| 375 |
-
}}
|
| 376 |
-
}}
|
| 377 |
-
|
| 378 |
loadStats();
|
|
|
|
|
|
|
| 379 |
loadMetrics();
|
| 380 |
loadRAGInfo();
|
| 381 |
-
loadRecentQueries();
|
| 382 |
loadStatus();
|
| 383 |
setInterval(loadStatus, 30000);
|
|
|
|
| 384 |
setInterval(loadRecentQueries, 60000);
|
| 385 |
</script>
|
| 386 |
</body></html>""")
|
|
@@ -467,16 +496,6 @@ async def metrics(request: Request):
|
|
| 467 |
return {"error": str(e)}
|
| 468 |
|
| 469 |
|
| 470 |
-
@router.get("/queries")
|
| 471 |
-
async def recent_queries(request: Request, limit: int = 50):
|
| 472 |
-
_require_session(request)
|
| 473 |
-
try:
|
| 474 |
-
from app import get_metrics_service
|
| 475 |
-
return {"queries": get_metrics_service().recent_queries(limit=min(limit, 200))}
|
| 476 |
-
except Exception as e:
|
| 477 |
-
return {"queries": [], "error": str(e)}
|
| 478 |
-
|
| 479 |
-
|
| 480 |
@router.get("/status")
|
| 481 |
async def system_status(request: Request):
|
| 482 |
_require_session(request)
|
|
|
|
| 259 |
</div>
|
| 260 |
|
| 261 |
<div class="card">
|
| 262 |
+
<h2>📈 Cloudflare Analytics (Persistent)</h2>
|
| 263 |
+
<div class="stat-grid" id="cfStatsGrid"><div style="color:#6b7280;font-size:.84rem">Loading from Cloudflare D1…</div></div>
|
| 264 |
+
<div style="margin-top:14px" id="cfToolsTable"><div style="color:#6b7280;font-size:.84rem">Loading tool breakdown…</div></div>
|
| 265 |
+
</div>
|
| 266 |
+
|
| 267 |
+
<div class="card">
|
| 268 |
+
<h2>📋 Recent Queries (Cloudflare D1)</h2>
|
| 269 |
<div id="recentTable" style="overflow-x:auto"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
|
| 270 |
</div>
|
| 271 |
|
| 272 |
+
|
| 273 |
<div class="card">
|
| 274 |
<h2>🔌 System Status</h2>
|
| 275 |
<div id="sysStatus"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
|
|
|
|
| 277 |
|
| 278 |
</div>
|
| 279 |
<script>
|
| 280 |
+
const WORKER_URL = "https://steep-haze-e1f8.nikeshn.workers.dev";
|
| 281 |
+
|
| 282 |
async function loadStats(){{
|
| 283 |
try{{
|
| 284 |
const r=await fetch('/admin/metrics');
|
| 285 |
const d=await r.json();
|
| 286 |
document.getElementById('statsGrid').innerHTML=`
|
| 287 |
+
<div class="stat"><div class="val">${{d.agent_requests||0}}</div><div class="lbl">Requests (session)</div></div>
|
| 288 |
+
<div class="stat"><div class="val">${{d.search_requests||0}}</div><div class="lbl">Searches (session)</div></div>
|
| 289 |
+
<div class="stat"><div class="val">${{d.follow_up_hits||0}}</div><div class="lbl">Follow-ups (session)</div></div>
|
| 290 |
+
<div class="stat"><div class="val">${{d.feedback_total||0}}</div><div class="lbl">Feedback (session)</div></div>
|
|
|
|
| 291 |
`;
|
| 292 |
+
}}catch(e){{document.getElementById('statsGrid').innerHTML='<span style="color:#dc2626">Local metrics unavailable</span>';}}
|
| 293 |
+
}}
|
| 294 |
+
|
| 295 |
+
async function loadCFAnalytics(){{
|
| 296 |
+
try{{
|
| 297 |
+
const r=await fetch(WORKER_URL+'/analytics');
|
| 298 |
+
const d=await r.json();
|
| 299 |
+
if(d.error) throw new Error(d.error);
|
| 300 |
+
document.getElementById('cfStatsGrid').innerHTML=`
|
| 301 |
+
<div class="stat"><div class="val">${{d.total||0}}</div><div class="lbl">Total Queries</div></div>
|
| 302 |
+
<div class="stat"><div class="val">${{d.today||0}}</div><div class="lbl">Today</div></div>
|
| 303 |
+
<div class="stat"><div class="val">${{d.week||0}}</div><div class="lbl">This Week</div></div>
|
| 304 |
+
<div class="stat"><div class="val">${{(d.avg_time||0).toFixed(2)}}s</div><div class="lbl">Avg Response</div></div>
|
| 305 |
+
<div class="stat"><div class="val">${{d.errors||0}}</div><div class="lbl">Errors</div></div>
|
| 306 |
+
`;
|
| 307 |
+
const tools=(d.tools||[]).slice(0,15);
|
| 308 |
+
let rows=tools.map(t=>`<tr><td>${{t.tool_used||"unknown"}}</td><td>${{t.c}}</td></tr>`).join('');
|
| 309 |
+
document.getElementById('cfToolsTable').innerHTML=rows
|
| 310 |
+
?`<table><thead><tr><th>Intent:Tool</th><th>Count</th></tr></thead><tbody>${{rows}}</tbody></table>`
|
| 311 |
+
:'<span style="color:#6b7280;font-size:.84rem">No data yet.</span>';
|
| 312 |
+
}}catch(e){{
|
| 313 |
+
document.getElementById('cfStatsGrid').innerHTML='<span style="color:#dc2626">Cloudflare D1 unavailable: '+e.message+'</span>';
|
| 314 |
+
}}
|
| 315 |
+
}}
|
| 316 |
+
|
| 317 |
+
async function loadRecentQueries(){{
|
| 318 |
+
try{{
|
| 319 |
+
const r=await fetch(WORKER_URL+'/analytics/recent');
|
| 320 |
+
const d=await r.json();
|
| 321 |
+
if(d.error) throw new Error(d.error);
|
| 322 |
+
const rows=(d.results||[]).map(q=>`<tr>
|
| 323 |
+
<td style="white-space:nowrap;font-size:.74rem">${{q.timestamp||""}}</td>
|
| 324 |
+
<td style="max-width:280px;word-break:break-word">${{(q.question||"").substring(0,100)}}</td>
|
| 325 |
+
<td>${{q.tool_used||""}}</td>
|
| 326 |
+
<td>${{q.model||""}}</td>
|
| 327 |
+
<td style="white-space:nowrap">${{(q.response_time||0).toFixed(2)}}s</td>
|
| 328 |
+
<td>${{q.result_count||0}}</td>
|
| 329 |
+
</tr>`).join('');
|
| 330 |
+
document.getElementById('recentTable').innerHTML=rows
|
| 331 |
+
?`<table><thead><tr><th>Time</th><th>Question</th><th>Tool</th><th>Model</th><th>Time</th><th>Results</th></tr></thead><tbody>${{rows}}</tbody></table>`
|
| 332 |
+
:'<span style="color:#6b7280;font-size:.84rem">No queries logged yet.</span>';
|
| 333 |
+
}}catch(e){{
|
| 334 |
+
document.getElementById('recentTable').innerHTML='<span style="color:#dc2626">Could not load: '+e.message+'</span>';
|
| 335 |
+
}}
|
| 336 |
}}
|
| 337 |
|
| 338 |
async function loadMetrics(){{
|
|
|
|
| 347 |
.map(([k,v])=>`<tr><td style="color:#dc2626">${{k}}</td><td>${{v}}</td><td style="color:#6b7280">error</td></tr>`).join('');
|
| 348 |
document.getElementById('metricsTable').innerHTML=rows
|
| 349 |
?`<table><thead><tr><th>Key</th><th>Count</th><th>Type</th></tr></thead><tbody>${{rows}}</tbody></table>`
|
| 350 |
+
:'<span style="color:#6b7280;font-size:.84rem">No session data yet — resets on deploy.</span>';
|
| 351 |
}}catch(e){{document.getElementById('metricsTable').innerHTML='<span style="color:#dc2626">Could not load metrics</span>';}}
|
| 352 |
}}
|
| 353 |
|
|
|
|
| 402 |
}}
|
| 403 |
}}
|
| 404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
loadStats();
|
| 406 |
+
loadCFAnalytics();
|
| 407 |
+
loadRecentQueries();
|
| 408 |
loadMetrics();
|
| 409 |
loadRAGInfo();
|
|
|
|
| 410 |
loadStatus();
|
| 411 |
setInterval(loadStatus, 30000);
|
| 412 |
+
setInterval(loadCFAnalytics, 60000);
|
| 413 |
setInterval(loadRecentQueries, 60000);
|
| 414 |
</script>
|
| 415 |
</body></html>""")
|
|
|
|
| 496 |
return {"error": str(e)}
|
| 497 |
|
| 498 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
@router.get("/status")
|
| 500 |
async def system_status(request: Request):
|
| 501 |
_require_session(request)
|