Add Renderers page for L1 server management

- Add user_renderers table to track which L1 servers users are attached to
- Add L1_SERVERS config to define available renderers
- Add /renderers page showing attachment status for each L1 server
- Add attach functionality (redirects to L1 /auth with token)
- Add detach functionality with HTMX updates
- Add Renderers link to nav bar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-09 17:18:41 +00:00
parent 990ac44108
commit 28843ea185
2 changed files with 157 additions and 0 deletions

111
server.py
View File

@@ -48,6 +48,10 @@ L1_PUBLIC_URL = os.environ.get("L1_PUBLIC_URL", "https://celery-artdag.rose-ash.
EFFECTS_REPO_URL = os.environ.get("EFFECTS_REPO_URL", "https://git.rose-ash.com/art-dag/effects")
IPFS_GATEWAY_URL = os.environ.get("IPFS_GATEWAY_URL", "")
# Known L1 renderers (comma-separated URLs)
L1_SERVERS_STR = os.environ.get("L1_SERVERS", "https://celery-artdag.rose-ash.com")
L1_SERVERS = [s.strip() for s in L1_SERVERS_STR.split(",") if s.strip()]
# Cookie domain for sharing auth across subdomains (e.g., ".rose-ash.com")
# If not set, derives from DOMAIN (strips first subdomain, adds leading dot)
def _get_cookie_domain():
@@ -319,6 +323,7 @@ def base_html(title: str, content: str, username: str = None) -> str:
<a href="/activities" class="text-gray-400 hover:text-white transition-colors">Activities</a>
<a href="/users" class="text-gray-400 hover:text-white transition-colors">Users</a>
<a href="/anchors/ui" class="text-gray-400 hover:text-white transition-colors">Anchors</a>
<a href="/renderers" class="text-gray-400 hover:text-white transition-colors">Renderers</a>
<a href="/download/client" class="text-gray-400 hover:text-white transition-colors ml-auto" title="Download CLI client">Download Client</a>
</nav>
@@ -2824,6 +2829,112 @@ async def test_ots_connection():
''')
# ============ Renderers (L1 servers) ============
@app.get("/renderers", response_class=HTMLResponse)
async def renderers_page(request: Request):
"""Page to manage L1 renderer attachments."""
username = get_user_from_cookie(request)
if not username:
content = '''
<h2 class="text-xl font-semibold text-white mb-4">Renderers</h2>
<p class="text-gray-400">Log in to manage your renderer connections.</p>
'''
return HTMLResponse(base_html("Renderers", content))
# Get user's attached renderers
attached = await db.get_user_renderers(username)
token = request.cookies.get("auth_token", "")
# Build renderer list
rows = []
for l1_url in L1_SERVERS:
is_attached = l1_url in attached
# Extract display name from URL
display_name = l1_url.replace("https://", "").replace("http://", "")
if is_attached:
status = '<span class="px-2 py-1 bg-green-600 text-white text-xs font-medium rounded-full">Attached</span>'
action = f'''
<a href="{l1_url}" target="_blank" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors">
Open
</a>
<button hx-post="/renderers/detach" hx-vals='{{"l1_url": "{l1_url}"}}' hx-target="#renderer-{l1_url.replace("://", "-").replace("/", "-").replace(".", "-")}" hx-swap="outerHTML"
class="px-3 py-1 bg-gray-600 hover:bg-gray-700 text-white text-sm rounded transition-colors ml-2">
Detach
</button>
'''
else:
status = '<span class="px-2 py-1 bg-gray-600 text-gray-300 text-xs font-medium rounded-full">Not attached</span>'
# Attach redirects to L1 with token
attach_url = f"{l1_url}/auth?auth_token={token}"
action = f'''
<a href="{attach_url}" class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
onclick="setTimeout(() => location.reload(), 2000)">
Attach
</a>
'''
row_id = l1_url.replace("://", "-").replace("/", "-").replace(".", "-")
rows.append(f'''
<div id="renderer-{row_id}" class="flex items-center justify-between p-4 bg-dark-600 rounded-lg">
<div>
<div class="font-medium text-white">{display_name}</div>
<div class="text-sm text-gray-400">{l1_url}</div>
</div>
<div class="flex items-center gap-3">
{status}
{action}
</div>
</div>
''')
content = f'''
<h2 class="text-xl font-semibold text-white mb-4">Renderers</h2>
<p class="text-gray-400 mb-6">Connect to L1 rendering servers. After attaching, you can run effects and manage media on that renderer.</p>
<div class="space-y-3">
{"".join(rows) if rows else '<p class="text-gray-500">No renderers configured.</p>'}
</div>
'''
return HTMLResponse(base_html("Renderers", content, username))
@app.post("/renderers/detach", response_class=HTMLResponse)
async def detach_renderer(request: Request):
"""Detach from an L1 renderer."""
username = get_user_from_cookie(request)
if not username:
return HTMLResponse('<div class="text-red-400">Not logged in</div>')
form = await request.form()
l1_url = form.get("l1_url", "")
await db.detach_renderer(username, l1_url)
# Return updated row
display_name = l1_url.replace("https://", "").replace("http://", "")
token = request.cookies.get("auth_token", "")
attach_url = f"{l1_url}/auth?auth_token={token}"
row_id = l1_url.replace("://", "-").replace("/", "-").replace(".", "-")
return HTMLResponse(f'''
<div id="renderer-{row_id}" class="flex items-center justify-between p-4 bg-dark-600 rounded-lg">
<div>
<div class="font-medium text-white">{display_name}</div>
<div class="text-sm text-gray-400">{l1_url}</div>
</div>
<div class="flex items-center gap-3">
<span class="px-2 py-1 bg-gray-600 text-gray-300 text-xs font-medium rounded-full">Not attached</span>
<a href="{attach_url}" class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
onclick="setTimeout(() => location.reload(), 2000)">
Attach
</a>
</div>
</div>
''')
# ============ Client Download ============
CLIENT_TARBALL = Path(__file__).parent / "artdag-client.tar.gz"