import os import shutil import logging import tempfile from fastapi import FastAPI, Request, File, UploadFile, Form from fastapi.responses import HTMLResponse, RedirectResponse, FileResponse from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("overlay-manager") # root_path ensures FastAPI knows it lives at /patch-manager/ app = FastAPI(root_path="/patch-manager") os.makedirs("static", exist_ok=True) 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 = [] dirs = set([""]) if os.path.exists(OVERLAY_DIR): for root, dirnames, filenames in os.walk(OVERLAY_DIR): rel_root = os.path.relpath(root, OVERLAY_DIR) if rel_root != ".": dirs.add(rel_root) 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), "dirs": sorted(list(dirs)) }) @app.get("/download-all") async def download_all(): """Zips the entire /srv directory and serves it as a backup.""" with tempfile.TemporaryDirectory() as tmpdir: zip_base = os.path.join(tmpdir, "backup") shutil.make_archive(zip_base, 'zip', OVERLAY_DIR) return FileResponse( path=f"{zip_base}.zip", filename="ardupilot_overlays_backup.zip", media_type='application/zip' ) @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) return RedirectResponse(url="./", status_code=303) @app.post("/create_folder") async def create_folder(folder_path: str = Form(...)): save_dir = os.path.join(OVERLAY_DIR, folder_path.strip("/")) os.makedirs(save_dir, exist_ok=True) return RedirectResponse(url="./", status_code=303) @app.post("/delete") async def delete_file(filepath: str = Form(...)): target = os.path.join(OVERLAY_DIR, filepath.lstrip("/")) if os.path.exists(target): if os.path.isdir(target): shutil.rmtree(target) else: os.remove(target) return RedirectResponse(url="./", status_code=303) @app.get("/edit", response_class=HTMLResponse) async def edit_file(request: Request, filepath: str): clean_path = filepath.lstrip("/") target = os.path.join(OVERLAY_DIR, clean_path) logger.info(f"Attempting to edit: {target}") if not os.path.exists(target): return HTMLResponse(content=f"
File {target} missing.
Back", status_code=404) try: with open(target, "r", encoding="utf-8", errors="ignore") as f: content = f.read() except Exception as e: return HTMLResponse(content=f"{str(e)}
", status_code=500) 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.lstrip("/")) if os.path.exists(target): with open(target, "w", encoding="utf-8") as f: f.write(content) return RedirectResponse(url="./", status_code=303)