test script be
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s

This commit is contained in:
2026-03-01 10:47:48 +01:00
parent ed54050d6b
commit 60634a77eb

View File

@@ -19,7 +19,8 @@ import shutil
import subprocess
import time
import logging
from typing import Optional, List, Dict, Any
from typing import Optional, List, Dict, Any, Tuple, DefaultDict
from collections import defaultdict
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Query
from fastapi.responses import FileResponse, JSONResponse
@@ -377,7 +378,7 @@ class ScriptWithStatus(ScriptInfo):
- mappings: list of UnitMapping for that script
- requirements_exists: whether a requirements file exists on disk
"""
mappings: List[UnitMapping] = []
mappings: List[UnitMapping] = Field(default_factory=list)
requirements_exists: bool = False
@@ -405,6 +406,35 @@ class OperationResult(BaseModel):
python: Optional[str] = None
# ---------- Helper: group parsed units by script name ----------
def _group_units_by_script(units: List[str]) -> Dict[str, List[Tuple[str, UnitParsed, bool]]]:
"""
Parse each unit and group by parsed.name (script name). Returns a dict:
{ script_name: [ (svc, parsed, active), ... ], ... }
If parsed.name is None, attempt to extract script name from service string using pattern.
"""
groups: DefaultDict[str, List[Tuple[str, UnitParsed, bool]]] = defaultdict(list)
for svc in units:
parsed = parse_unit_execstart(svc)
try:
active = is_unit_active(svc)
except Exception:
active = False
script_name = parsed.name
if not script_name:
# attempt to extract from service name like 'fw-script-<name>-q<N>'
m = re.match(rf'^{re.escape(UNIT_PREFIX)}-(?P<name>[A-Za-z0-9_.-]+)-q\d+$', svc)
if m:
script_name = m.group("name")
if not script_name:
# fallback: group under special key so they won't be lost; use svc as key
script_name = svc
groups[script_name].append((svc, parsed, active))
return groups
# ---------- Endpoints ----------
# NOTE: combined status info into GET /scripts below (replaces separate /status & /{name}/status endpoints)
@@ -420,27 +450,23 @@ def list_scripts_with_status() -> List[ScriptWithStatus]:
"""
out: List[ScriptWithStatus] = []
# build a list of units once for efficiency
# build a list of units once for efficiency and group them by parsed script name
all_units = list_fw_units()
logger.debug("list_scripts_with_status: discovered %d fw units", len(all_units))
grouped = _group_units_by_script(all_units)
# iterate script files on disk
for fn in os.listdir(SCRIPT_DIR):
if not fn.endswith(".py"):
continue
name = fn.rsplit(".", 1)[0]
spath = os.path.join(SCRIPT_DIR, fn)
# find units matching this script
prefix = f"{UNIT_PREFIX}-{name}-q"
mappings: List[UnitMapping] = []
for svc in all_units:
if svc.startswith(prefix):
parsed = parse_unit_execstart(svc)
try:
active = is_unit_active(svc)
except Exception:
active = False
mappings.append(UnitMapping(service=svc, parsed=parsed, active=active))
# take groups[name] if present
entries = grouped.get(name, [])
for svc, parsed, active in entries:
mappings.append(UnitMapping(service=svc, parsed=parsed, active=active))
req_exists = os.path.exists(requirements_path_for(name))
out.append(ScriptWithStatus(name=name, path=spath, mappings=mappings, requirements_exists=req_exists))
@@ -520,6 +546,9 @@ async def upload_script(
logger.exception("Cleanup after pip failure partially failed for %s", name)
# Return pip failure as 500 with message
raise HTTPException(status_code=500, detail=f"pip install failed: {err_msg}")
else:
# If no requirements provided on upload, we simply keep uploaded script. (Per earlier conversation you can require requirements always if you want.)
logger.debug("No requirements uploaded with script %s", name)
except HTTPException:
raise
except Exception as e: