import html from fastapi import APIRouter, Form from fastapi.responses import HTMLResponse from ..catalog import load_apps, save_apps from ..config import ( DEFAULT_APPFACTORY_HOST, DEFAULT_GITEA_ORG, DELETE_APP_SCRIPT, DEPLOY_SCRIPT, GENERATE_COMPOSE_SCRIPT, NEW_APP_SCRIPT, read_env_value, ) from ..shell import run_command from ..templates.layout import page, render_result router = APIRouter() @router.get("/", response_class=HTMLResponse) def index(): apps = load_apps() gitea_url = read_env_value("GITEA_URL", "") gitea_org = read_env_value("GITEA_ORG", DEFAULT_GITEA_ORG) host = read_env_value("APPFACTORY_HOST", DEFAULT_APPFACTORY_HOST) rows = "" for item in apps: app_id = html.escape(item.get("id", "")) status = html.escape(item.get("status", "")) docs = html.escape(item.get("docs", f"/apps/{app_id}/docs")) memory = item.get("memory", "") cpus = item.get("cpus", "") http_clone = html.escape(f"git clone {gitea_url}/{gitea_org}/{app_id}.git") ssh_clone = html.escape(f"git clone ssh://git@{host}:2222/{gitea_org}/{app_id}.git") memory_options = "" for value, label in [ ("", "Default"), ("256m", "256 MB - small service"), ("512m", "512 MB - normal service"), ("1g", "1 GB - larger service"), ("2g", "2 GB - heavy service"), ]: selected = "selected" if memory == value else "" memory_options += f'' cpu_options = "" for value, label in [ ("", "Default"), ("0.25", "0.25 CPU - very small"), ("0.50", "0.50 CPU - normal"), ("1.00", "1 CPU - full core"), ("2.00", "2 CPU - heavy"), ]: selected = "selected" if cpus == value else "" cpu_options += f'' rows += f""" {app_id}
/apps/{app_id} {status} Swagger
Memory is RAM limit. CPU is max compute share. Example: 0.50 = half CPU core, 1.00 = one full core.
Clone commands
""" if not rows: rows = 'No apps deployed yet.' return page( "Apps", f"""

Apps

Create, deploy, clone, tune resources and delete services.

+ New App

Backups

Create backups and copy restore commands. Restore is intentionally manual and guarded.

Manage Backups

Deployed Apps

{rows}
App Status Docs Resources Git Actions
""", ) @router.get("/new-app", response_class=HTMLResponse) def new_app_form(): return page( "New App", """

Create New App

This creates Gitea repo, webhook, local workspace, initial commit and deploys the service.




← Back

""", ) @router.post("/new-app", response_class=HTMLResponse) def create_app(app_id: str = Form(...), app_name: str = Form(...), template: str = Form(...)): if template != "python-fastapi": return HTMLResponse("Unsupported template", status_code=400) create_result = run_command([NEW_APP_SCRIPT, app_id, app_name]) deploy_result = run_command([DEPLOY_SCRIPT, app_id]) status = "OK" if create_result.returncode == 0 and deploy_result.returncode == 0 else "FAILED" return render_result( title=f"Create App: {status}", back_url="/portal", sections=[ ("Create Output", create_result.stdout), ("Create Error", create_result.stderr), ("Deploy Output", deploy_result.stdout), ("Deploy Error", deploy_result.stderr), ], extra_link=f"/apps/{html.escape(app_id)}/docs", extra_label="Open Swagger", ) @router.post("/delete-app", response_class=HTMLResponse) def delete_app(app_id: str = Form(...)): result = run_command([DELETE_APP_SCRIPT, app_id]) status = "OK" if result.returncode == 0 else "FAILED" return render_result( title=f"Delete App: {status}", back_url="/portal", sections=[("Output", result.stdout), ("Error", result.stderr)], ) @router.post("/update-resources", response_class=HTMLResponse) def update_resources(app_id: str = Form(...), memory: str = Form(""), cpus: str = Form("")): apps = load_apps() for item in apps: if item.get("id") == app_id: memory = memory.strip() cpus = cpus.strip() if memory: item["memory"] = memory else: item.pop("memory", None) if cpus: item["cpus"] = cpus else: item.pop("cpus", None) save_apps(apps) compose_result = run_command([GENERATE_COMPOSE_SCRIPT]) deploy_result = run_command([DEPLOY_SCRIPT, app_id]) status = "OK" if compose_result.returncode == 0 and deploy_result.returncode == 0 else "FAILED" return render_result( title=f"Update Resources: {status}", back_url="/portal", sections=[ ("Compose Output", compose_result.stdout), ("Compose Error", compose_result.stderr), ("Deploy Output", deploy_result.stdout), ("Deploy Error", deploy_result.stderr), ], )