Show logged-in user on home page instead of login button

Convert static HOME_HTML to render_home_html() function that
dynamically shows user info with link to L2 profile when
authenticated, or login button when not logged in.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-08 17:51:09 +00:00
parent c917a0539d
commit 591c0aad4c

View File

@@ -419,7 +419,21 @@ async def api_info():
}
HOME_HTML = """
def render_home_html(actor_id: Optional[str] = None) -> str:
"""Render the home page HTML with optional user info."""
if actor_id:
# Extract username and domain from @username@domain format
parts = actor_id.lstrip("@").split("@")
username = parts[0] if parts else actor_id
domain = parts[1] if len(parts) > 1 else ""
l2_user_url = f"https://{domain}/users/{username}" if domain else "#"
user_section = f'''<div class="ml-auto flex items-center gap-2 text-sm text-gray-300">
Logged in as <a href="{l2_user_url}" class="text-blue-400 hover:text-blue-300">{actor_id}</a>
</div>'''
else:
user_section = '''<a href="/login" class="ml-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md text-white font-medium transition-colors">Login via L2</a>'''
return f"""
<!DOCTYPE html>
<html class="dark">
<head>
@@ -428,10 +442,10 @@ HOME_HTML = """
<title>Art DAG L1 Server</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
tailwind.config = {{
darkMode: 'class',
theme: { extend: { colors: { dark: { 900: '#0a0a0a', 800: '#111', 700: '#1a1a1a', 600: '#222', 500: '#333' } } } }
}
theme: {{ extend: {{ colors: {{ dark: {{ 900: '#0a0a0a', 800: '#111', 700: '#1a1a1a', 600: '#222', 500: '#333' }} }} }} }}
}}
</script>
</head>
<body class="bg-dark-900 text-gray-100 min-h-screen">
@@ -441,7 +455,7 @@ HOME_HTML = """
<a href="/recipes" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-blue-400 hover:text-blue-300 font-medium transition-colors">Recipes</a>
<a href="/media" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-blue-400 hover:text-blue-300 font-medium transition-colors">Media</a>
<a href="/docs" class="px-4 py-2 bg-dark-500 hover:bg-dark-600 rounded-md text-blue-400 hover:text-blue-300 font-medium transition-colors">API Docs</a>
<a href="/login" class="ml-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md text-white font-medium transition-colors">Login</a>
{user_section}
</nav>
<h1 class="text-3xl font-bold text-white border-b border-dark-500 pb-4 mb-6">Art DAG L1 Server</h1>
@@ -481,16 +495,16 @@ HOME_HTML = """
<h2 class="text-xl font-semibold text-gray-200 mt-8 mb-4">Start a Run</h2>
<pre class="bg-dark-700 p-4 rounded-lg overflow-x-auto border border-dark-500 mb-8"><code class="text-green-300">curl -X POST /runs \\
-H "Content-Type: application/json" \\
-d '{"recipe": "dog", "inputs": ["33268b6e..."]}'</code></pre>
-d '{{"recipe": "dog", "inputs": ["33268b6e..."]}}'</code></pre>
<h2 class="text-xl font-semibold text-gray-200 mt-8 mb-4">Provenance</h2>
<p class="text-gray-300 mb-4">Every render produces a provenance record linking inputs, effects, and infrastructure:</p>
<pre class="bg-dark-700 p-4 rounded-lg overflow-x-auto border border-dark-500"><code class="text-green-300">{
"output": {"content_hash": "..."},
<pre class="bg-dark-700 p-4 rounded-lg overflow-x-auto border border-dark-500"><code class="text-green-300">{{
"output": {{"content_hash": "..."}},
"inputs": [...],
"effects": [...],
"infrastructure": {...}
}</code></pre>
"infrastructure": {{...}}
}}</code></pre>
</div>
</body>
</html>
@@ -498,9 +512,11 @@ HOME_HTML = """
@app.get("/", response_class=HTMLResponse)
async def root():
async def root(request: Request):
"""Home page."""
return HOME_HTML
ctx = get_user_context_from_cookie(request)
actor_id = ctx.actor_id if ctx else None
return render_home_html(actor_id)
@app.post("/runs", response_model=RunStatus)
@@ -729,7 +745,7 @@ async def run_detail(run_id: str, request: Request):
if wants_html(request):
ctx = get_user_context_from_cookie(request)
if not ctx:
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to view run details.</p>'
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to view run details.</p>'
return HTMLResponse(render_page("Login Required", content, None, active_tab="runs"), status_code=401)
# Check user owns this run
@@ -964,7 +980,7 @@ async def list_runs(request: Request, page: int = 1, limit: int = 20):
if wants_html(request):
if not ctx:
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to see your runs.</p>'
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to see your runs.</p>'
return HTMLResponse(render_page("Runs", content, None, active_tab="runs"))
if not runs_page:
@@ -1147,7 +1163,7 @@ async def list_recipes_api(request: Request, page: int = 1, limit: int = 20):
if not ctx:
return HTMLResponse(render_page(
"Recipes",
'<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to see recipes.</p>',
'<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to see recipes.</p>',
None,
active_tab="recipes"
))
@@ -1520,7 +1536,7 @@ async def ui_recipes_list(request: Request):
ctx = get_user_context_from_cookie(request)
if not ctx:
return '<p class="text-gray-400 py-8 text-center"><a href="/ui/login" class="text-blue-400 hover:text-blue-300">Login</a> to see recipes.</p>'
return '<p class="text-gray-400 py-8 text-center"><a href="/ui/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to see recipes.</p>'
all_recipes = list_all_recipes()
@@ -1681,7 +1697,7 @@ async def cache_detail(content_hash: str, request: Request):
if wants_html(request):
if not ctx:
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to view cached content.</p>'
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to view cached content.</p>'
return HTMLResponse(render_page("Login Required", content, None, active_tab="media"), status_code=401)
# Check user has access
@@ -2190,7 +2206,7 @@ async def list_media(
if wants_html(request):
# Require login for HTML media view
if not ctx:
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to see media.</p>'
content = '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to see media.</p>'
return HTMLResponse(render_page("Media", content, None, active_tab="media"))
# Get hashes owned by/associated with this user
@@ -3078,16 +3094,20 @@ def render_page(title: str, content: str, actor_id: Optional[str] = None, active
"""
user_info = ""
if actor_id:
# Extract username and domain from @username@domain format
parts = actor_id.lstrip("@").split("@")
username = parts[0] if parts else actor_id
domain = parts[1] if len(parts) > 1 else ""
l2_user_url = f"https://{domain}/users/{username}" if domain else "#"
user_info = f'''
<div class="flex items-center gap-4 text-sm text-gray-400">
Logged in as <strong class="text-white">{actor_id}</strong>
<a href="/logout" class="text-blue-400 hover:text-blue-300">Logout</a>
Logged in as <a href="{l2_user_url}" class="text-white hover:text-blue-300">{actor_id}</a>
</div>
'''
else:
user_info = '''
<div class="text-sm">
<a href="/login" class="text-blue-400 hover:text-blue-300">Login</a>
<a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a>
</div>
'''
@@ -3135,16 +3155,20 @@ def render_ui_html(actor_id: Optional[str] = None, tab: str = "runs") -> str:
"""
user_info = ""
if actor_id:
# Extract username and domain from @username@domain format
parts = actor_id.lstrip("@").split("@")
username = parts[0] if parts else actor_id
domain = parts[1] if len(parts) > 1 else ""
l2_user_url = f"https://{domain}/users/{username}" if domain else "#"
user_info = f'''
<div class="flex items-center gap-4 text-sm text-gray-400">
Logged in as <strong class="text-white">{actor_id}</strong>
<a href="/logout" class="text-blue-400 hover:text-blue-300">Logout</a>
Logged in as <a href="{l2_user_url}" class="text-white hover:text-blue-300">{actor_id}</a>
</div>
'''
else:
user_info = '''
<div class="text-sm">
<a href="/login" class="text-blue-400 hover:text-blue-300">Login</a>
<a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a>
</div>
'''
@@ -3327,7 +3351,7 @@ async def ui_runs(request: Request):
# Require login to see runs
if not ctx:
return '<p class="text-gray-400 py-8 text-center"><a href="/ui/login" class="text-blue-400 hover:text-blue-300">Login</a> to see your runs.</p>'
return '<p class="text-gray-400 py-8 text-center"><a href="/ui/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to see your runs.</p>'
# Filter runs by user - match both plain username and ActivityPub format (@user@domain)
runs = [r for r in runs if r.username in (ctx.username, ctx.actor_id)]
@@ -3426,7 +3450,7 @@ async def ui_media_list(
# Require login to see media
if not ctx:
return '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login</a> to see media.</p>'
return '<p class="text-gray-400 py-8 text-center"><a href="/login" class="text-blue-400 hover:text-blue-300">Login via L2</a> to see media.</p>'
# Get hashes owned by/associated with this user
user_hashes = await get_user_cache_hashes(ctx.username, ctx.actor_id)