import os import shutil from fastapi import FastAPI, Request, File, UploadFile, Form from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles # ===================================================================== # MODIFICATION: ROOT PATH FOR NGINX # Why: When Nginx routes traffic from yourdomain.com/patch-manager/ # to this sidecar, FastAPI needs to know its "base" URL is no longer "/". # If we don't set this, the app will try to load CSS and submit forms # to the wrong root URL, resulting in 404 errors. # ===================================================================== app = FastAPI(root_path="/patch-manager") app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates") OVERLAY_DIR = "/srv" @app.get("/", response_class=HTMLResponse) async def index(request: Request): files = [] if os.path.exists(OVERLAY_DIR): for root, dirs, filenames in os.walk(OVERLAY_DIR): for filename in filenames: rel_path = os.path.relpath(os.path.join(root, filename), OVERLAY_DIR) files.append(rel_path) return templates.TemplateResponse("index.html", {"request": request, "files": sorted(files)}) @app.post("/upload") async def upload_file(file: UploadFile = File(...), target_path: str = Form("")): save_dir = os.path.join(OVERLAY_DIR, target_path.strip("/")) os.makedirs(save_dir, exist_ok=True) file_location = os.path.join(save_dir, file.filename) with open(file_location, "wb+") as file_object: shutil.copyfileobj(file.file, file_object) # Notice we redirect to "/" here. Because we set root_path="/patch-manager", # FastAPI automatically translates this redirect to "/patch-manager/"! return RedirectResponse(url="/", status_code=303) @app.post("/delete") async def delete_file(filepath: str = Form(...)): target = os.path.join(OVERLAY_DIR, filepath) if os.path.exists(target): os.remove(target) target_dir = os.path.dirname(target) if not os.listdir(target_dir) and target_dir != OVERLAY_DIR: os.rmdir(target_dir) return RedirectResponse(url="/", status_code=303) @app.get("/edit", response_class=HTMLResponse) async def edit_file(request: Request, filepath: str): target = os.path.join(OVERLAY_DIR, filepath) content = "" if os.path.exists(target): with open(target, "r", encoding="utf-8", errors="ignore") as f: content = f.read() return templates.TemplateResponse("edit.html", {"request": request, "filepath": filepath, "content": content}) @app.post("/edit") async def save_file(filepath: str = Form(...), content: str = Form(...)): target = os.path.join(OVERLAY_DIR, filepath) if os.path.exists(target): with open(target, "w", encoding="utf-8") as f: f.write(content) return RedirectResponse(url="/", status_code=303)