rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
57babb2
Β·
1 Parent(s): 769576f

feat: admin LLM control panel as in-app tab (D-022)

Browse files

Previously the LLM control surface lived at /admin/llm-control.html β€” a
separate URL that had to be remembered + opened in a fresh tab. This
surfaces it as a header tab matching the existing UI pattern, with the
panel iframe-embedded inside the right-side aside drawer.

- New showAdmin state + slate-gradient button ("Admin Β· Access panel")
positioned next to the language toggle.
- Toggling Admin closes any other panel (marketplace/premium/profile).
- Toggling any other panel closes Admin (mutual exclusion via existing pattern).
- iframe sandbox: allow-scripts + allow-same-origin + allow-forms so the
control HTML can call /api/admin/* on the same origin.
- Backend admin API stays IP-gated (ADMIN_IP_ALLOWLIST), so non-allowlisted
visitors see the control HTML but its API calls return 404 β€” the panel
silently shows "not authorized" state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. frontend/src/app/page.tsx +53 -4
frontend/src/app/page.tsx CHANGED
@@ -62,6 +62,10 @@ export default function Page() {
62
  const [showPremium, setShowPremium] = useState(false);
63
  const [showMarketplace, setShowMarketplace] = useState(false);
64
  const [showProfile, setShowProfile] = useState(false);
 
 
 
 
65
  const [marketplace, setMarketplace] = useState<MarketplaceResponse | null>(null);
66
  const [openPolicy, setOpenPolicy] = useState<MarketplacePolicy | null>(null);
67
  const [sessionId, setSessionId] = useState<string | undefined>();
@@ -370,7 +374,7 @@ export default function Page() {
370
  </div>
371
  <div className="flex items-center gap-2 sm:gap-3">
372
  <button
373
- onClick={() => { setShowMarketplace(!showMarketplace); setShowPremium(false); setShowCoverage(false); }}
374
  className={`group relative overflow-hidden rounded-xl transition-all shadow-sm hover:shadow-md ${
375
  showMarketplace
376
  ? "ring-2 ring-[var(--primary)]"
@@ -402,7 +406,7 @@ export default function Page() {
402
  </div>
403
  </button>
404
  <button
405
- onClick={() => { setShowPremium(!showPremium); setShowMarketplace(false); setShowCoverage(false); setShowProfile(false); }}
406
  className={`group relative overflow-hidden rounded-xl transition-all shadow-sm hover:shadow-md ${
407
  showPremium ? "ring-2 ring-[var(--primary)]" : ""
408
  }`}
@@ -420,7 +424,7 @@ export default function Page() {
420
  </div>
421
  </button>
422
  <button
423
- onClick={() => { setShowProfile(!showProfile); setShowMarketplace(false); setShowPremium(false); setShowCoverage(false); }}
424
  className={`group relative overflow-hidden rounded-xl transition-all shadow-sm hover:shadow-md ${
425
  showProfile ? "ring-2 ring-[var(--primary)]" : ""
426
  }`}
@@ -443,6 +447,27 @@ export default function Page() {
443
  )}
444
  </div>
445
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  {/* UI language toggle β€” flips visual chrome + voice TTS together */}
447
  <button
448
  onClick={() => setTtsLang(ttsLang === "en-IN" ? "hi-IN" : "en-IN")}
@@ -552,7 +577,7 @@ export default function Page() {
552
  {/* Panel column β€” sits beside the chat on desktop, takes over on
553
  mobile. Stays mounted as long as a panel is open; chat in the
554
  other column remains fully interactive (real-time copilot). */}
555
- {(showMarketplace || showPremium || showProfile) && (
556
  <aside className="lg:w-3/5 w-full overflow-y-auto bg-[var(--background)]">
557
  {showMarketplace && marketplace && (
558
  <MarketplacePanel
@@ -574,6 +599,30 @@ export default function Page() {
574
  uiLang={uiLang}
575
  />
576
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
  </aside>
578
  )}
579
  </div>
 
62
  const [showPremium, setShowPremium] = useState(false);
63
  const [showMarketplace, setShowMarketplace] = useState(false);
64
  const [showProfile, setShowProfile] = useState(false);
65
+ // Admin panel: iframe-embedded LLM control surface. Backend admin API is
66
+ // IP-gated (ADMIN_IP_ALLOWLIST), so the panel itself silently renders the
67
+ // dashboard's "not authorized" view for non-allowlisted IPs.
68
+ const [showAdmin, setShowAdmin] = useState(false);
69
  const [marketplace, setMarketplace] = useState<MarketplaceResponse | null>(null);
70
  const [openPolicy, setOpenPolicy] = useState<MarketplacePolicy | null>(null);
71
  const [sessionId, setSessionId] = useState<string | undefined>();
 
374
  </div>
375
  <div className="flex items-center gap-2 sm:gap-3">
376
  <button
377
+ onClick={() => { setShowMarketplace(!showMarketplace); setShowPremium(false); setShowCoverage(false); setShowAdmin(false); }}
378
  className={`group relative overflow-hidden rounded-xl transition-all shadow-sm hover:shadow-md ${
379
  showMarketplace
380
  ? "ring-2 ring-[var(--primary)]"
 
406
  </div>
407
  </button>
408
  <button
409
+ onClick={() => { setShowPremium(!showPremium); setShowMarketplace(false); setShowCoverage(false); setShowProfile(false); setShowAdmin(false); }}
410
  className={`group relative overflow-hidden rounded-xl transition-all shadow-sm hover:shadow-md ${
411
  showPremium ? "ring-2 ring-[var(--primary)]" : ""
412
  }`}
 
424
  </div>
425
  </button>
426
  <button
427
+ onClick={() => { setShowProfile(!showProfile); setShowMarketplace(false); setShowPremium(false); setShowCoverage(false); setShowAdmin(false); }}
428
  className={`group relative overflow-hidden rounded-xl transition-all shadow-sm hover:shadow-md ${
429
  showProfile ? "ring-2 ring-[var(--primary)]" : ""
430
  }`}
 
447
  )}
448
  </div>
449
  </button>
450
+ {/* Admin access β€” opens the LLM control panel in an embedded view.
451
+ Backend admin API is IP-gated, so the panel works only from the
452
+ allowlisted home IP; from other networks it shows "not authorized". */}
453
+ <button
454
+ onClick={() => { setShowAdmin(!showAdmin); setShowMarketplace(false); setShowPremium(false); setShowProfile(false); setShowCoverage(false); }}
455
+ className={`group relative overflow-hidden rounded-xl transition-all shadow-sm hover:shadow-md ${
456
+ showAdmin ? "ring-2 ring-[var(--primary)]" : ""
457
+ }`}
458
+ title="LLM control panel β€” health, chain order, usage (admin-only, IP-gated)"
459
+ >
460
+ <div className="absolute inset-0 bg-gradient-to-br from-slate-700 via-slate-600 to-zinc-700" />
461
+ <div className="relative flex items-stretch text-white">
462
+ <div className="flex items-center justify-center px-3 py-2 bg-black/15">
463
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2 4 6v6c0 5 3.5 9 8 10 4.5-1 8-5 8-10V6l-8-4z" /><path d="M9 12l2 2 4-4" /></svg>
464
+ </div>
465
+ <div className="px-3 py-2 text-left">
466
+ <div className="text-[10px] uppercase tracking-wider opacity-85 leading-none">Admin</div>
467
+ <div className="text-xs font-bold leading-tight whitespace-nowrap">Access panel</div>
468
+ </div>
469
+ </div>
470
+ </button>
471
  {/* UI language toggle β€” flips visual chrome + voice TTS together */}
472
  <button
473
  onClick={() => setTtsLang(ttsLang === "en-IN" ? "hi-IN" : "en-IN")}
 
577
  {/* Panel column β€” sits beside the chat on desktop, takes over on
578
  mobile. Stays mounted as long as a panel is open; chat in the
579
  other column remains fully interactive (real-time copilot). */}
580
+ {(showMarketplace || showPremium || showProfile || showAdmin) && (
581
  <aside className="lg:w-3/5 w-full overflow-y-auto bg-[var(--background)]">
582
  {showMarketplace && marketplace && (
583
  <MarketplacePanel
 
599
  uiLang={uiLang}
600
  />
601
  )}
602
+ {showAdmin && (
603
+ <div className="flex flex-col h-full">
604
+ <div className="flex items-center justify-between px-4 py-3 border-b border-[var(--border)] bg-[var(--card)]">
605
+ <div>
606
+ <h2 className="text-sm font-semibold">Admin Β· LLM Control Panel</h2>
607
+ <p className="text-xs text-[var(--muted-foreground)]">
608
+ IP-gated. Only the home network IP can interact with chain reordering / probes.
609
+ </p>
610
+ </div>
611
+ <button
612
+ onClick={() => setShowAdmin(false)}
613
+ className="text-xs text-[var(--muted-foreground)] hover:underline"
614
+ >
615
+ close
616
+ </button>
617
+ </div>
618
+ <iframe
619
+ src="/admin/llm-control.html"
620
+ title="LLM Control Panel"
621
+ className="flex-1 w-full border-0 bg-white"
622
+ sandbox="allow-scripts allow-same-origin allow-forms"
623
+ />
624
+ </div>
625
+ )}
626
  </aside>
627
  )}
628
  </div>