From a0cdf31c365389d8d330eabd03f723647efd93ba Mon Sep 17 00:00:00 2001 From: gilesb Date: Wed, 7 Jan 2026 21:18:52 +0000 Subject: [PATCH] Add asset detail and user detail UI pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /ui/asset/{name}: Shows full asset info (owner, hash, origin, tags, description, ActivityPub URL) - /ui/user/{username}: Shows user profile with their published assets and activity stats - Updated users list to link to user detail pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- server.py | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 215 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index 01aac81..dca9a50 100644 --- a/server.py +++ b/server.py @@ -513,10 +513,18 @@ async def ui_registry_page(request: Request): rows = "" for name, asset in sorted(assets.items(), key=lambda x: x[1].get("created_at", ""), reverse=True): hash_short = asset.get("content_hash", "")[:16] + "..." + owner = asset.get("owner", "unknown") + asset_type = asset.get("asset_type", "") + type_color = "bg-blue-600" if asset_type == "image" else "bg-purple-600" if asset_type == "video" else "bg-gray-600" rows += f''' - - {name} - {asset.get("asset_type", "")} + + + {name} + + {asset_type} + + {owner} + {hash_short} {", ".join(asset.get("tags", []))} @@ -529,6 +537,7 @@ async def ui_registry_page(request: Request): Name Type + Owner Content Hash Tags @@ -601,12 +610,11 @@ async def ui_users_page(request: Request): ''' else: rows = "" - for username, user_data in sorted(users.items()): - actor_url = f"https://{DOMAIN}/users/{username}" - webfinger = f"@{username}@{DOMAIN}" + for uname, user_data in sorted(users.items()): + webfinger = f"@{uname}@{DOMAIN}" rows += f''' - - {username} + + {uname} {webfinger} {user_data.get("created_at", "")[:10]} @@ -632,6 +640,205 @@ async def ui_users_page(request: Request): return HTMLResponse(base_html("Users", content, current_user)) +@app.get("/ui/asset/{name}", response_class=HTMLResponse) +async def ui_asset_detail(name: str, request: Request): + """Asset detail page.""" + username = get_user_from_cookie(request) + registry = load_registry() + assets = registry.get("assets", {}) + + if name not in assets: + content = f''' +

Asset Not Found

+

No asset named "{name}" exists.

+

← Back to Registry

+ ''' + return HTMLResponse(base_html("Asset Not Found", content, username)) + + asset = assets[name] + owner = asset.get("owner", "unknown") + content_hash = asset.get("content_hash", "") + asset_type = asset.get("asset_type", "") + tags = asset.get("tags", []) + description = asset.get("description", "") + origin = asset.get("origin", {}) + created_at = asset.get("created_at", "")[:10] + + type_color = "bg-blue-600" if asset_type == "image" else "bg-purple-600" if asset_type == "video" else "bg-gray-600" + + # Origin display + origin_html = "" + if origin: + origin_type = origin.get("type", "unknown") + if origin_type == "self": + origin_html = 'Original content by author' + elif origin_type == "external": + origin_url = origin.get("url", "") + origin_note = origin.get("note", "") + origin_html = f''' + {origin_url} + ''' + if origin_note: + origin_html += f'

{origin_note}

' + + # Tags display + tags_html = "" + if tags: + tags_html = " ".join([f'{t}' for t in tags]) + else: + tags_html = 'No tags' + + content = f''' +

← Back to Registry

+ +
+

{name}

+ {asset_type} +
+ +
+
+
+

Owner

+ {owner} +
+ +
+

Content Hash

+ {content_hash} +
+ +
+

Created

+ {created_at} +
+
+ +
+
+

Description

+

{description if description else 'No description'}

+
+ +
+

Origin

+ {origin_html if origin_html else 'Not specified'} +
+ +
+

Tags

+
{tags_html}
+
+
+
+ +
+

ActivityPub

+

+ Object URL: https://{DOMAIN}/objects/{content_hash} +

+
+ ''' + return HTMLResponse(base_html(f"Asset: {name}", content, username)) + + +@app.get("/ui/user/{username}", response_class=HTMLResponse) +async def ui_user_detail(username: str, request: Request): + """User detail page showing their published assets.""" + current_user = get_user_from_cookie(request) + + if not user_exists(username): + content = f''' +

User Not Found

+

No user named "{username}" exists.

+

← Back to Users

+ ''' + return HTMLResponse(base_html("User Not Found", content, current_user)) + + # Get user's assets + registry = load_registry() + all_assets = registry.get("assets", {}) + user_assets = {name: asset for name, asset in all_assets.items() if asset.get("owner") == username} + + # Get user's activities + all_activities = load_activities() + actor_id = f"https://{DOMAIN}/users/{username}" + user_activities = [a for a in all_activities if a.get("actor_id") == actor_id] + + webfinger = f"@{username}@{DOMAIN}" + + # Assets table + if user_assets: + rows = "" + for name, asset in sorted(user_assets.items(), key=lambda x: x[1].get("created_at", ""), reverse=True): + hash_short = asset.get("content_hash", "")[:16] + "..." + asset_type = asset.get("asset_type", "") + type_color = "bg-blue-600" if asset_type == "image" else "bg-purple-600" if asset_type == "video" else "bg-gray-600" + rows += f''' + + + {name} + + {asset_type} + {hash_short} + {", ".join(asset.get("tags", []))} + + ''' + assets_html = f''' +
+ + + + + + + + + + + {rows} + +
NameTypeContent HashTags
+
+ ''' + else: + assets_html = '

No published assets yet.

' + + content = f''' +

← Back to Users

+ +
+

{username}

+ {webfinger} +
+ +
+
+
{len(user_assets)}
+
Published Assets
+
+
+
{len(user_activities)}
+
Activities
+
+
+ +
+

ActivityPub

+

+ Actor URL: https://{DOMAIN}/users/{username} +

+

+ Outbox: https://{DOMAIN}/users/{username}/outbox +

+
+ +

Published Assets ({len(user_assets)})

+ {assets_html} + ''' + return HTMLResponse(base_html(f"User: {username}", content, current_user)) + + # ============ API Endpoints ============ @app.get("/")