diff --git a/server.py b/server.py index de5f4a5..9e8729d 100644 --- a/server.py +++ b/server.py @@ -191,9 +191,9 @@ HOME_HTML = """
Run not found: {run_id}
' + return HTMLResponse(render_page("Not Found", content, None, active_tab="runs"), status_code=404) + raise HTTPException(404, f"Run {run_id} not found") + + # Check Celery task status if running + if run.status == "running" and run.celery_task_id: + task = celery_app.AsyncResult(run.celery_task_id) + if task.ready(): + if task.successful(): + result = task.result + run.status = "completed" + run.completed_at = datetime.now(timezone.utc).isoformat() + run.output_hash = result.get("output", {}).get("content_hash") + effects = result.get("effects", []) + if effects: + run.effects_commit = effects[0].get("repo_commit") + run.effect_url = effects[0].get("repo_url") + run.infrastructure = result.get("infrastructure") + output_path = Path(result.get("output", {}).get("local_path", "")) + if output_path.exists(): + cache_file(output_path) + else: + run.status = "failed" + run.error = str(task.result) + save_run(run) + + if wants_html(request): + current_user = get_user_from_cookie(request) + if not current_user: + content = 'Login to view run details.
' + return HTMLResponse(render_page("Login Required", content, current_user, active_tab="runs"), status_code=401) + + # Check user owns this run + actor_id = f"@{current_user}@{L2_DOMAIN}" + if run.username not in (current_user, actor_id): + content = 'Access denied.
' + return HTMLResponse(render_page("Access Denied", content, current_user, active_tab="runs"), status_code=403) + + # Build effect URL + if run.effect_url: + effect_url = run.effect_url + elif run.effects_commit and run.effects_commit != "unknown": + effect_url = f"https://git.rose-ash.com/art-dag/effects/src/commit/{run.effects_commit}/{run.recipe}" + else: + effect_url = f"https://git.rose-ash.com/art-dag/effects/src/branch/main/{run.recipe}" + + # Status badge colors + status_colors = { + "completed": "bg-green-600 text-white", + "running": "bg-yellow-600 text-white", + "failed": "bg-red-600 text-white", + "pending": "bg-gray-600 text-white" + } + status_badge = status_colors.get(run.status, "bg-gray-600 text-white") + + # Build media HTML for input/output + media_html = "" + has_input = run.inputs and (CACHE_DIR / run.inputs[0]).exists() + has_output = run.status == "completed" and run.output_hash and (CACHE_DIR / run.output_hash).exists() + + if has_input or has_output: + media_html = 'Unknown format
' + media_html += f''' + + ''' + if has_output: + output_hash = run.output_hash + output_media_type = detect_media_type(CACHE_DIR / output_hash) + output_video_src = video_src_for_request(output_hash, request) + if output_media_type == "video": + output_elem = f'' + elif output_media_type == "image": + output_elem = f'Unknown format
' + media_html += f''' + + ''' + media_html += 'Register this transformation output on the L2 ActivityPub server.
+ + +Login to see your runs.
' + return HTMLResponse(render_page("Runs", content, current_user, active_tab="runs")) + + if not runs_page: + if page == 1: + content = 'You have no runs yet. Use the CLI to start a run.
' + else: + return HTMLResponse("") # Empty for infinite scroll + else: + # Status badge colors + status_colors = { + "completed": "bg-green-600 text-white", + "running": "bg-yellow-600 text-white", + "failed": "bg-red-600 text-white", + "pending": "bg-gray-600 text-white" + } + + html_parts = [] + for run in runs_page: + status_badge = status_colors.get(run.status, "bg-gray-600 text-white") + html_parts.append(f''' + +Loading more...
+Loading more...
+Login to view cached content.
-Content not found: {content_hash}
' + return HTMLResponse(render_page("Not Found", content, current_user, active_tab="cache"), status_code=404) + raise HTTPException(404, f"Content {content_hash} not in cache") - media_type = detect_media_type(cache_path) - file_size = cache_path.stat().st_size - size_str = f"{file_size:,} bytes" - if file_size > 1024*1024: - size_str = f"{file_size/(1024*1024):.1f} MB" - elif file_size > 1024: - size_str = f"{file_size/1024:.1f} KB" + if wants_html(request): + if not current_user: + content = 'Login to view cached content.
' + return HTMLResponse(render_page("Login Required", content, current_user, active_tab="cache"), status_code=401) - # Build media display HTML - if media_type == "video": - video_src = video_src_for_request(content_hash, request) - media_html = f'' - elif media_type == "image": - media_html = f'Unknown file type. Download file
' + # Check user has access + user_hashes = get_user_cache_hashes(current_user) + if content_hash not in user_hashes: + content = 'Access denied.
' + return HTMLResponse(render_page("Access Denied", content, current_user, active_tab="cache"), status_code=403) - html = f""" - - - - - -Unknown file type. Download file
' + + content = f''' + @@ -563,15 +865,38 @@ async def ui_cache_view(content_hash: str, request: Request):Login to see cached content.
' + return HTMLResponse(render_page("Cache", content, current_user, active_tab="cache")) + + # Get hashes owned by/associated with this user + user_hashes = get_user_cache_hashes(current_user) + + # Get cache items that belong to the user + cache_items = [] + if CACHE_DIR.exists(): + for f in CACHE_DIR.iterdir(): + if f.is_file() and not f.name.endswith('.provenance.json') and not f.name.endswith('.meta.json') and not f.name.endswith('.mp4'): + if f.name in user_hashes: + meta = load_cache_meta(f.name) + + # Apply folder filter + if folder: + item_folder = meta.get("folder", "/") + if folder != "/" and not item_folder.startswith(folder): + continue + if folder == "/" and item_folder != "/": + continue + + # Apply collection filter + if collection: + if collection not in meta.get("collections", []): + continue + + # Apply tag filter + if tag: + if tag not in meta.get("tags", []): + continue + + stat = f.stat() + cache_items.append({ + "hash": f.name, + "size": stat.st_size, + "mtime": stat.st_mtime, + "meta": meta + }) + + # Sort by modification time (newest first) + cache_items.sort(key=lambda x: x["mtime"], reverse=True) + total = len(cache_items) + + # Pagination + start = (page - 1) * limit + end = start + limit + items_page = cache_items[start:end] + has_more = end < total + + if not items_page: + if page == 1: + filter_msg = "" + if folder: + filter_msg = f" in folder {folder}" + elif collection: + filter_msg = f" in collection '{collection}'" + elif tag: + filter_msg = f" with tag '{tag}'" + content = f'No cached files{filter_msg}. Upload files or run effects to see them here.
' + else: + return HTMLResponse("") # Empty for infinite scroll + else: + html_parts = [] + for item in items_page: + content_hash = item["hash"] + cache_path = CACHE_DIR / content_hash + media_type = detect_media_type(cache_path) + + # Format size + size = item["size"] + if size > 1024*1024: + size_str = f"{size/(1024*1024):.1f} MB" + elif size > 1024: + size_str = f"{size/1024:.1f} KB" + else: + size_str = f"{size} bytes" + + html_parts.append(f''' + +Unknown file type
') + + html_parts.append('Loading more...
+Loading more...
+