diff --git a/llm_team_ui.py b/llm_team_ui.py index 425e4c2..77dc365 100644 --- a/llm_team_ui.py +++ b/llm_team_ui.py @@ -70,13 +70,18 @@ LOGIN_RATE_MAX = 5 # IPs that never get rate-limited (your LAN, localhost) ALLOWLIST_IPS = {"127.0.0.1", "::1", "192.168.1.1"} # Demo mode state — toggled by admin at runtime -_demo_mode = {"active": False, "started_by": None} +_demo_mode = {"active": False, "started_by": None, "showcase": False} -# Admin-only write routes — blocked in demo for non-admin users -ADMIN_WRITE_ROUTES = { - "/api/admin/config": ["POST"], - "/api/admin/test-provider": ["POST"], - "/api/auth/login": ["POST"], +# Routes that demo users CAN trigger (read-like POSTs — enrichment, self-analysis, team runs) +DEMO_ALLOWED_POSTS = { + "/api/run", "/api/self-analyze", "/api/admin/security/enrich", +} + +# Routes that demo users CANNOT touch (destructive writes) +DEMO_BLOCKED_POSTS = { + "/api/admin/config", "/api/admin/test-provider", "/api/admin/security/ban", + "/api/admin/security/mass-ban", "/api/demo/toggle", "/api/demo/allowlist", + "/api/runs/bulk-archive", "/api/meta-pipeline", } @@ -123,10 +128,14 @@ def login_required(f): def admin_required(f): @wraps(f) def decorated(*args, **kwargs): - # Demo mode: allow read access (GET), block writes unless admin + # Demo/showcase mode: full read access to everything if is_demo(): if request.method == "GET": return f(*args, **kwargs) + # Allow specific read-like POSTs (enrichment, self-analysis, team runs) + if request.path in DEMO_ALLOWED_POSTS: + return f(*args, **kwargs) + # Block destructive writes if not is_admin(): return jsonify({"error": "demo mode: read-only", "demo": True}), 403 if not session.get("user_id"): @@ -444,16 +453,28 @@ def logout_page(): @app.route("/api/demo/status") def demo_status(): - return jsonify({"active": is_demo(), "started_by": _demo_mode.get("started_by")}) + return jsonify({"active": is_demo(), "showcase": _demo_mode.get("showcase", False), "started_by": _demo_mode.get("started_by")}) @app.route("/api/demo/toggle", methods=["POST"]) def demo_toggle(): if not session.get("user_id") or not is_admin(): return jsonify({"error": "admin only"}), 403 - _demo_mode["active"] = not _demo_mode["active"] - _demo_mode["started_by"] = session.get("username") if _demo_mode["active"] else None - return jsonify({"active": _demo_mode["active"]}) + data = request.json if request.is_json else {} + mode = data.get("mode", "toggle") # toggle, showcase, off + if mode == "off": + _demo_mode["active"] = False + _demo_mode["showcase"] = False + _demo_mode["started_by"] = None + elif mode == "showcase": + _demo_mode["active"] = True + _demo_mode["showcase"] = True + _demo_mode["started_by"] = session.get("username") + else: + _demo_mode["active"] = not _demo_mode["active"] + _demo_mode["showcase"] = _demo_mode["active"] + _demo_mode["started_by"] = session.get("username") if _demo_mode["active"] else None + return jsonify({"active": _demo_mode["active"], "showcase": _demo_mode["showcase"]}) @app.route("/api/demo/allowlist", methods=["GET"]) @@ -2707,27 +2728,42 @@ async function deleteRun(id) { // ─── DEMO MODE ─────────────────────────────── async function checkDemo() { try { - const r = await fetch('/api/demo/status'); - const d = await r.json(); - updateDemoUI(d.active); + var r = await fetch('/api/demo/status'); + var d = await r.json(); + updateDemoUI(d.active, d.showcase); } catch(e) {} } -function updateDemoUI(active) { - const btn = document.getElementById('demo-toggle'); - const banner = document.getElementById('demo-banner'); +function updateDemoUI(active, showcase) { + var btn = document.getElementById('demo-toggle'); + var banner = document.getElementById('demo-banner'); if (btn) { btn.style.display = ''; - btn.textContent = active ? 'Demo ON' : 'Demo'; - btn.style.color = active ? '#22c55e' : 'var(--orange)'; - btn.style.borderColor = active ? 'rgba(34,197,94,0.4)' : 'rgba(245,158,11,0.3)'; + if (showcase) { + btn.textContent = 'Showcase'; + btn.style.color = '#d946ef'; + btn.style.borderColor = 'rgba(217,70,239,0.4)'; + } else if (active) { + btn.textContent = 'Demo ON'; + btn.style.color = '#4ade80'; + btn.style.borderColor = 'rgba(74,222,128,0.4)'; + } else { + btn.textContent = 'Demo'; + btn.style.color = 'var(--orange)'; + btn.style.borderColor = 'rgba(245,158,11,0.3)'; + } } if (active) { if (!banner) { - const b = document.createElement('div'); + var b = document.createElement('div'); b.id = 'demo-banner'; - b.style.cssText = 'position:fixed;top:0;left:0;right:0;background:linear-gradient(90deg,rgba(34,197,94,0.08),rgba(34,197,94,0.15),rgba(34,197,94,0.08));border-bottom:1px solid rgba(34,197,94,0.25);color:#22c55e;text-align:center;font-size:12px;padding:6px;z-index:50;font-weight:600;letter-spacing:1px'; - b.textContent = 'DEMO MODE'; + if (showcase) { + b.style.cssText = 'position:fixed;top:0;left:0;right:0;background:linear-gradient(90deg,rgba(217,70,239,0.05),rgba(217,70,239,0.12),rgba(217,70,239,0.05));border-bottom:2px solid rgba(217,70,239,0.3);color:#d946ef;text-align:center;font-size:11px;padding:8px;z-index:50;font-weight:700;letter-spacing:2px;font-family:JetBrains Mono,monospace;text-transform:uppercase'; + b.textContent = 'Showcase Mode — Full Read-Only Access — Admin · Monitor · Logs · Lab · History'; + } else { + b.style.cssText = 'position:fixed;top:0;left:0;right:0;background:linear-gradient(90deg,rgba(74,222,128,0.05),rgba(74,222,128,0.1),rgba(74,222,128,0.05));border-bottom:1px solid rgba(74,222,128,0.25);color:#4ade80;text-align:center;font-size:11px;padding:6px;z-index:50;font-weight:600;letter-spacing:1px;font-family:JetBrains Mono,monospace;text-transform:uppercase'; + b.textContent = 'Demo Mode'; + } document.body.prepend(b); } } else if (banner) { @@ -2963,11 +2999,14 @@ ADMIN_HTML = r"""
When active, the public can view and use the Team UI, Lab, and all modes without logging in. Admin settings (API keys, config saves) are read-only for non-admins.
+Showcase mode gives visitors full read-only access to everything — Admin, Monitor, Logs, Threat Intel, Lab, History. They can run teams, view enrichments, and trigger self-analysis reports. They cannot change settings, ban IPs, delete data, or modify configs. Perfect for demos.