nikeshn commited on
Commit
f8c042e
·
verified ·
1 Parent(s): 90cd241

Update src/api/admin.py

Browse files
Files changed (1) hide show
  1. src/api/admin.py +35 -12
src/api/admin.py CHANGED
@@ -266,6 +266,15 @@ async def admin_dashboard(request: Request):
266
  mm_on = "selected" if cfg.get("maintenance_mode") else ""
267
  mm_off = "" if cfg.get("maintenance_mode") else "selected"
268
 
 
 
 
 
 
 
 
 
 
269
  return HTMLResponse(f"""<!DOCTYPE html>
270
  <html><head><meta charset="utf-8"><title>KU Library AI — Admin</title>
271
  <meta name="viewport" content="width=device-width,initial-scale=1">
@@ -357,8 +366,26 @@ tr:last-child td{{border-bottom:none}}
357
 
358
  </div>
359
  <script>
360
- // Analytics are proxied through the backend (/admin/analytics*) so the
361
- // Worker URL and bearer token never reach the browser.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
  async function loadStats(){{
364
  try{{
@@ -375,10 +402,8 @@ async function loadStats(){{
375
 
376
  async function loadCFAnalytics(){{
377
  try{{
378
- const r=await fetch('/admin/analytics');
379
- const d=await r.json();
380
- if(!r.ok||d.detail) throw new Error('HTTP '+r.status+' '+(d.detail||''));
381
- if(d.error) throw new Error(d.error.message||JSON.stringify(d.error));
382
  document.getElementById('cfStatsGrid').innerHTML=`
383
  <div class="stat"><div class="val">${{d.total||0}}</div><div class="lbl">Total Queries</div></div>
384
  <div class="stat"><div class="val">${{d.today||0}}</div><div class="lbl">Today</div></div>
@@ -397,10 +422,8 @@ async function loadCFAnalytics(){{
397
 
398
  async function loadRecentQueries(){{
399
  try{{
400
- const r=await fetch('/admin/analytics/recent');
401
- const d=await r.json();
402
- if(!r.ok||d.detail) throw new Error('HTTP '+r.status+' '+(d.detail||''));
403
- if(d.error) throw new Error(d.error.message||JSON.stringify(d.error));
404
  if(d.error) throw new Error(d.error);
405
  const rows=(d.results||[]).map(q=>`<tr>
406
  <td style="white-space:nowrap;font-size:.74rem">${{q.timestamp||""}}</td>
@@ -698,7 +721,7 @@ async def _worker_get(path: str, params: Optional[dict] = None):
698
  if settings.cloudflare_worker_token:
699
  headers["Authorization"] = f"Bearer {settings.cloudflare_worker_token}"
700
  try:
701
- async with _mk(timeout=10) as client:
702
  r = await client.get(f"{worker}{path}", params=params or {}, headers=headers)
703
  return r.json()
704
  except HTTPException:
@@ -756,7 +779,7 @@ async def admin_worker_health(request: Request):
756
  """One probe, its own short timeout, never raises."""
757
  entry = {"path": path}
758
  try:
759
- async with _mk(timeout=6) as client:
760
  r = await client.get(f"{worker}{path}",
761
  headers=headers if use_auth else {})
762
  entry["status"] = r.status_code
 
266
  mm_on = "selected" if cfg.get("maintenance_mode") else ""
267
  mm_off = "" if cfg.get("maintenance_mode") else "selected"
268
 
269
+ # Worker credentials injected into this page only. The dashboard tries the
270
+ # server-side proxy first and falls back to a direct browser -> Worker call,
271
+ # because some hosting environments (HF Spaces) cannot reach workers.dev
272
+ # while the admin's own browser can. This page is served exclusively to an
273
+ # authenticated admin session, so the token is not publicly exposed.
274
+ _s = get_settings()
275
+ cf_url = (_s.cloudflare_worker_url or "").strip().strip('"').strip("'").rstrip("/")
276
+ cf_token = (_s.cloudflare_worker_token or "").strip()
277
+
278
  return HTMLResponse(f"""<!DOCTYPE html>
279
  <html><head><meta charset="utf-8"><title>KU Library AI — Admin</title>
280
  <meta name="viewport" content="width=device-width,initial-scale=1">
 
366
 
367
  </div>
368
  <script>
369
+ const CF_URL = "{cf_url}";
370
+ const CF_TOKEN = "{cf_token}";
371
+
372
+ // Two-path analytics fetch. The backend proxy keeps credentials server-side and
373
+ // is tried first; if the backend cannot reach the Worker (a known limitation on
374
+ // some hosts) we retry directly from this browser, which can.
375
+ async function cfFetch(path){{
376
+ try{{
377
+ const r = await fetch('/admin'+path);
378
+ const d = await r.json();
379
+ if(r.ok && !d.detail && !d.error) return {{data:d, via:'backend proxy'}};
380
+ }}catch(e){{ /* fall through to direct */ }}
381
+ if(!CF_URL) throw new Error('CLOUDFLARE_WORKER_URL not configured');
382
+ const h = CF_TOKEN ? {{'Authorization':'Bearer '+CF_TOKEN}} : {{}};
383
+ const r2 = await fetch(CF_URL+path, {{headers:h}});
384
+ const d2 = await r2.json();
385
+ if(!r2.ok) throw new Error('Worker HTTP '+r2.status+' '+(d2.error&&d2.error.message||''));
386
+ if(d2.error) throw new Error(d2.error.message||'Worker error');
387
+ return {{data:d2, via:'direct'}};
388
+ }}
389
 
390
  async function loadStats(){{
391
  try{{
 
402
 
403
  async function loadCFAnalytics(){{
404
  try{{
405
+ const res=await cfFetch('/analytics');
406
+ const d=res.data;
 
 
407
  document.getElementById('cfStatsGrid').innerHTML=`
408
  <div class="stat"><div class="val">${{d.total||0}}</div><div class="lbl">Total Queries</div></div>
409
  <div class="stat"><div class="val">${{d.today||0}}</div><div class="lbl">Today</div></div>
 
422
 
423
  async function loadRecentQueries(){{
424
  try{{
425
+ const res=await cfFetch('/analytics/recent');
426
+ const d=res.data;
 
 
427
  if(d.error) throw new Error(d.error);
428
  const rows=(d.results||[]).map(q=>`<tr>
429
  <td style="white-space:nowrap;font-size:.74rem">${{q.timestamp||""}}</td>
 
721
  if settings.cloudflare_worker_token:
722
  headers["Authorization"] = f"Bearer {settings.cloudflare_worker_token}"
723
  try:
724
+ async with _mk(timeout=25) as client:
725
  r = await client.get(f"{worker}{path}", params=params or {}, headers=headers)
726
  return r.json()
727
  except HTTPException:
 
779
  """One probe, its own short timeout, never raises."""
780
  entry = {"path": path}
781
  try:
782
+ async with _mk(timeout=20) as client:
783
  r = await client.get(f"{worker}{path}",
784
  headers=headers if use_auth else {})
785
  entry["status"] = r.status_code