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:
111
server.py
111
server.py
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user