nikeshn commited on
Commit
3c8bee9
Β·
verified Β·
1 Parent(s): e6b00c1

Update src/api/admin.py

Browse files
Files changed (1) hide show
  1. src/api/admin.py +43 -38
src/api/admin.py CHANGED
@@ -21,16 +21,38 @@ from src.config import get_settings
21
  from src.services.runtime_store import JsonRuntimeStore
22
 
23
  router = APIRouter()
24
- admin_sessions: Dict[str, float] = {} # in-memory cache
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
 
27
- # ── Persistent session store (plain JSON β€” no external dependencies) ──────────
28
 
29
  import json as _json
30
  import os as _os
31
 
32
  def _sessions_file() -> str:
33
- """Return path for persistent sessions file."""
34
  cfg_path = _os.environ.get("CONFIG_STORE_PATH", "")
35
  if cfg_path and "/data" in cfg_path:
36
  return _os.path.join(_os.path.dirname(cfg_path), "admin_sessions.json")
@@ -38,7 +60,6 @@ def _sessions_file() -> str:
38
 
39
 
40
  def _load_sessions() -> None:
41
- """Load persisted sessions into in-memory cache."""
42
  global admin_sessions
43
  try:
44
  with open(_sessions_file(), "r", encoding="utf-8") as _f:
@@ -48,11 +69,10 @@ def _load_sessions() -> None:
48
  if isinstance(exp, (int, float)) and exp > now}
49
  admin_sessions.update(valid)
50
  except Exception:
51
- pass # file missing or corrupt β€” start fresh
52
 
53
 
54
  def _save_sessions() -> None:
55
- """Persist valid sessions to disk."""
56
  try:
57
  now = time.time()
58
  valid = {t: exp for t, exp in admin_sessions.items() if exp > now}
@@ -62,19 +82,6 @@ def _save_sessions() -> None:
62
  pass
63
 
64
 
65
- # ── Session helpers ────────────────────────────────────────────────────────────
66
-
67
- def _cleanup_sessions() -> None:
68
- now = time.time()
69
- expired = [t for t, exp in list(admin_sessions.items()) if exp <= now]
70
- for t in expired:
71
- admin_sessions.pop(t, None)
72
- try:
73
- _load_sessions()
74
- except Exception:
75
- pass
76
-
77
-
78
  def _require_session(request: Request) -> None:
79
  _cleanup_sessions()
80
  token = request.cookies.get("admin_session")
@@ -294,10 +301,11 @@ tr:last-child td{{border-bottom:none}}
294
  <div id="metricsTable" style="overflow-x:auto"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
295
  </div>
296
 
 
297
  <div class="card">
298
  <h2>πŸ“ˆ Cloudflare Analytics (Persistent)</h2>
299
- <div class="stat-grid" id="cfStatsGrid"><div style="color:#6b7280;font-size:.84rem">Loading from Cloudflare D1…</div></div>
300
- <div style="margin-top:14px" id="cfToolsTable"><div style="color:#6b7280;font-size:.84rem">Loading tool breakdown…</div></div>
301
  </div>
302
 
303
  <div class="card">
@@ -305,7 +313,6 @@ tr:last-child td{{border-bottom:none}}
305
  <div id="recentTable" style="overflow-x:auto"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
306
  </div>
307
 
308
-
309
  <div class="card">
310
  <h2>πŸ”Œ System Status</h2>
311
  <div id="sysStatus"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
@@ -325,7 +332,7 @@ async function loadStats(){{
325
  <div class="stat"><div class="val">${{d.follow_up_hits||0}}</div><div class="lbl">Follow-ups (session)</div></div>
326
  <div class="stat"><div class="val">${{d.feedback_total||0}}</div><div class="lbl">Feedback (session)</div></div>
327
  `;
328
- }}catch(e){{document.getElementById('statsGrid').innerHTML='<span style="color:#dc2626">Local metrics unavailable</span>';}}
329
  }}
330
 
331
  async function loadCFAnalytics(){{
@@ -343,10 +350,9 @@ async function loadCFAnalytics(){{
343
  const tools=(d.tools||[]).slice(0,15);
344
  let rows=tools.map(t=>`<tr><td>${{t.tool_used||"unknown"}}</td><td>${{t.c}}</td></tr>`).join('');
345
  document.getElementById('cfToolsTable').innerHTML=rows
346
- ?`<table><thead><tr><th>Intent:Tool</th><th>Count</th></tr></thead><tbody>${{rows}}</tbody></table>`
347
- :'<span style="color:#6b7280;font-size:.84rem">No data yet.</span>';
348
  }}catch(e){{
349
- document.getElementById('cfStatsGrid').innerHTML='<span style="color:#dc2626">Cloudflare D1 unavailable: '+e.message+'</span>';
350
  }}
351
  }}
352
 
@@ -357,17 +363,17 @@ async function loadRecentQueries(){{
357
  if(d.error) throw new Error(d.error);
358
  const rows=(d.results||[]).map(q=>`<tr>
359
  <td style="white-space:nowrap;font-size:.74rem">${{q.timestamp||""}}</td>
360
- <td style="max-width:280px;word-break:break-word">${{(q.question||"").substring(0,100)}}</td>
361
  <td>${{q.tool_used||""}}</td>
362
  <td>${{q.model||""}}</td>
363
- <td style="white-space:nowrap">${{(q.response_time||0).toFixed(2)}}s</td>
364
  <td>${{q.result_count||0}}</td>
365
  </tr>`).join('');
366
  document.getElementById('recentTable').innerHTML=rows
367
- ?`<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>`
368
- :'<span style="color:#6b7280;font-size:.84rem">No queries logged yet.</span>';
369
  }}catch(e){{
370
- document.getElementById('recentTable').innerHTML='<span style="color:#dc2626">Could not load: '+e.message+'</span>';
371
  }}
372
  }}
373
 
@@ -383,8 +389,8 @@ async function loadMetrics(){{
383
  .map(([k,v])=>`<tr><td style="color:#dc2626">${{k}}</td><td>${{v}}</td><td style="color:#6b7280">error</td></tr>`).join('');
384
  document.getElementById('metricsTable').innerHTML=rows
385
  ?`<table><thead><tr><th>Key</th><th>Count</th><th>Type</th></tr></thead><tbody>${{rows}}</tbody></table>`
386
- :'<span style="color:#6b7280;font-size:.84rem">No session data yet β€” resets on deploy.</span>';
387
- }}catch(e){{document.getElementById('metricsTable').innerHTML='<span style="color:#dc2626">Could not load metrics</span>';}}
388
  }}
389
 
390
  async function loadRAGInfo(){{
@@ -444,9 +450,9 @@ loadRecentQueries();
444
  loadMetrics();
445
  loadRAGInfo();
446
  loadStatus();
447
- setInterval(loadStatus, 30000);
448
- setInterval(loadCFAnalytics, 60000);
449
- setInterval(loadRecentQueries, 60000);
450
  </script>
451
  </body></html>""")
452
 
@@ -455,7 +461,7 @@ setInterval(loadRecentQueries, 60000);
455
 
456
  @router.get("/public-config")
457
  async def public_config():
458
- """Public endpoint β€” no auth required. Returns safe fields for the frontend."""
459
  try:
460
  cfg = _load_config()
461
  except Exception:
@@ -468,7 +474,6 @@ async def public_config():
468
  }
469
 
470
 
471
-
472
  @router.get("/config")
473
  async def get_config(request: Request):
474
  _require_session(request)
 
21
  from src.services.runtime_store import JsonRuntimeStore
22
 
23
  router = APIRouter()
24
+ admin_sessions: Dict[str, float] = {}
25
+
26
+ _START_TIME = time.time()
27
+
28
+ _CONFIG_DEFAULTS: Dict[str, Any] = {
29
+ "max_results": 5,
30
+ "maintenance_mode": False,
31
+ "welcome_message": "Hi! I'm LibBee, the Khalifa University Library AI Assistant.",
32
+ "custom_instructions": "",
33
+ "announcement": "",
34
+ "maintenance_message": "LibBee is currently under maintenance. Please try again shortly.",
35
+ }
36
+
37
+
38
+ # ── Session helpers ────────────────────────────────────────────────────────────
39
+
40
+ def _cleanup_sessions() -> None:
41
+ now = time.time()
42
+ expired = [t for t, exp in admin_sessions.items() if exp <= now]
43
+ for t in expired:
44
+ admin_sessions.pop(t, None)
45
+ try:
46
+ _load_sessions()
47
+ except Exception:
48
+ pass
49
 
50
 
 
51
 
52
  import json as _json
53
  import os as _os
54
 
55
  def _sessions_file() -> str:
 
56
  cfg_path = _os.environ.get("CONFIG_STORE_PATH", "")
57
  if cfg_path and "/data" in cfg_path:
58
  return _os.path.join(_os.path.dirname(cfg_path), "admin_sessions.json")
 
60
 
61
 
62
  def _load_sessions() -> None:
 
63
  global admin_sessions
64
  try:
65
  with open(_sessions_file(), "r", encoding="utf-8") as _f:
 
69
  if isinstance(exp, (int, float)) and exp > now}
70
  admin_sessions.update(valid)
71
  except Exception:
72
+ pass
73
 
74
 
75
  def _save_sessions() -> None:
 
76
  try:
77
  now = time.time()
78
  valid = {t: exp for t, exp in admin_sessions.items() if exp > now}
 
82
  pass
83
 
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def _require_session(request: Request) -> None:
86
  _cleanup_sessions()
87
  token = request.cookies.get("admin_session")
 
301
  <div id="metricsTable" style="overflow-x:auto"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
302
  </div>
303
 
304
+
305
  <div class="card">
306
  <h2>πŸ“ˆ Cloudflare Analytics (Persistent)</h2>
307
+ <div class="stat-grid" id="cfStatsGrid"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
308
+ <div style="margin-top:14px" id="cfToolsTable"></div>
309
  </div>
310
 
311
  <div class="card">
 
313
  <div id="recentTable" style="overflow-x:auto"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
314
  </div>
315
 
 
316
  <div class="card">
317
  <h2>πŸ”Œ System Status</h2>
318
  <div id="sysStatus"><div style="color:#6b7280;font-size:.84rem">Loading…</div></div>
 
332
  <div class="stat"><div class="val">${{d.follow_up_hits||0}}</div><div class="lbl">Follow-ups (session)</div></div>
333
  <div class="stat"><div class="val">${{d.feedback_total||0}}</div><div class="lbl">Feedback (session)</div></div>
334
  `;
335
+ }}catch(e){{document.getElementById('statsGrid').innerHTML='<span style="color:#dc2626">Unavailable</span>';}}
336
  }}
337
 
338
  async function loadCFAnalytics(){{
 
350
  const tools=(d.tools||[]).slice(0,15);
351
  let rows=tools.map(t=>`<tr><td>${{t.tool_used||"unknown"}}</td><td>${{t.c}}</td></tr>`).join('');
352
  document.getElementById('cfToolsTable').innerHTML=rows
353
+ ?`<table><thead><tr><th>Intent:Tool</th><th>Count</th></tr></thead><tbody>${{rows}}</tbody></table>`:'';
 
354
  }}catch(e){{
355
+ document.getElementById('cfStatsGrid').innerHTML='<span style="color:#dc2626">CF D1 unavailable: '+e.message+'</span>';
356
  }}
357
  }}
358
 
 
363
  if(d.error) throw new Error(d.error);
364
  const rows=(d.results||[]).map(q=>`<tr>
365
  <td style="white-space:nowrap;font-size:.74rem">${{q.timestamp||""}}</td>
366
+ <td style="max-width:260px;word-break:break-word">${{(q.question||"").substring(0,100)}}</td>
367
  <td>${{q.tool_used||""}}</td>
368
  <td>${{q.model||""}}</td>
369
+ <td>${{(q.response_time||0).toFixed(2)}}s</td>
370
  <td>${{q.result_count||0}}</td>
371
  </tr>`).join('');
372
  document.getElementById('recentTable').innerHTML=rows
373
+ ?`<table><thead><tr><th>Time</th><th>Question</th><th>Tool</th><th>Model</th><th>Time</th><th>#</th></tr></thead><tbody>${{rows}}</tbody></table>`
374
+ :'<span style="color:#6b7280;font-size:.84rem">No queries yet.</span>';
375
  }}catch(e){{
376
+ document.getElementById('recentTable').innerHTML='<span style="color:#dc2626">'+e.message+'</span>';
377
  }}
378
  }}
379
 
 
389
  .map(([k,v])=>`<tr><td style="color:#dc2626">${{k}}</td><td>${{v}}</td><td style="color:#6b7280">error</td></tr>`).join('');
390
  document.getElementById('metricsTable').innerHTML=rows
391
  ?`<table><thead><tr><th>Key</th><th>Count</th><th>Type</th></tr></thead><tbody>${{rows}}</tbody></table>`
392
+ :'<span style="color:#6b7280;font-size:.84rem">No session data yet.</span>';
393
+ }}catch(e){{document.getElementById('metricsTable').innerHTML='<span style="color:#dc2626">Could not load</span>';}}
394
  }}
395
 
396
  async function loadRAGInfo(){{
 
450
  loadMetrics();
451
  loadRAGInfo();
452
  loadStatus();
453
+ setInterval(loadStatus,30000);
454
+ setInterval(loadCFAnalytics,60000);
455
+ setInterval(loadRecentQueries,60000);
456
  </script>
457
  </body></html>""")
458
 
 
461
 
462
  @router.get("/public-config")
463
  async def public_config():
464
+ """No auth β€” returns public fields for the chat frontend."""
465
  try:
466
  cfg = _load_config()
467
  except Exception:
 
474
  }
475
 
476
 
 
477
  @router.get("/config")
478
  async def get_config(request: Request):
479
  _require_session(request)