"""Startar flödesbyggarens webui på http://127.0.0.1:8765. python run_webui.py # startar; vägrar om porten redan används python run_webui.py --force # dödar en redan körande instans och tar över python run_webui.py --port 9000 Endast lokal åtkomst (127.0.0.1) - webui:t styr en riktig webbläsare mot din inloggade session och ska inte exponeras på nätverket. """ import argparse import socket import subprocess import sys import time HOST = "127.0.0.1" def _port_in_use(port: int) -> bool: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0) try: s.bind((HOST, port)) return False except OSError: return True def _pids_on_port(port: int) -> list[int]: """PID:erna som lyssnar på porten. Bäst effort, plattformsspecifikt.""" pids: set[int] = set() try: if sys.platform == "win32": out = subprocess.run( ["netstat", "-ano", "-p", "tcp"], capture_output=True, text=True, timeout=5 ).stdout for line in out.splitlines(): parts = line.split() if len(parts) >= 5 and parts[0].upper() == "TCP" and parts[1].endswith(f":{port}") \ and parts[3].upper() == "LISTENING": pids.add(int(parts[4])) else: out = subprocess.run( ["lsof", "-ti", f"tcp:{port}", "-sTCP:LISTEN"], capture_output=True, text=True, timeout=5 ).stdout pids.update(int(x) for x in out.split()) except Exception: # noqa: BLE001 pass return sorted(pids) def _kill(pids: list[int]) -> None: for pid in pids: try: if sys.platform == "win32": subprocess.run(["taskkill", "/F", "/PID", str(pid)], capture_output=True, timeout=5) else: subprocess.run(["kill", "-9", str(pid)], capture_output=True, timeout=5) print(f"Stängde process {pid}.") except Exception as exc: # noqa: BLE001 print(f"Kunde inte stänga {pid}: {exc}", file=sys.stderr) def main() -> None: parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--port", type=int, default=8765) parser.add_argument("--force", action="store_true", help="Döda en redan körande instans på porten och ta över") args = parser.parse_args() if _port_in_use(args.port): pids = _pids_on_port(args.port) who = f" (PID {', '.join(map(str, pids))})" if pids else "" if not args.force: print( f"Port {args.port} används redan{who}.\n" f" - webui:t kör troligen redan: öppna http://{HOST}:{args.port}\n" f" - annars: kör 'python run_webui.py --force' för att ta över, " f"eller '--port '.", file=sys.stderr, ) sys.exit(1) if pids: _kill(pids) time.sleep(1) if _port_in_use(args.port): print(f"Port {args.port} är fortfarande upptagen. Avbryter.", file=sys.stderr) sys.exit(1) import uvicorn uvicorn.run("webui.app:app", host=HOST, port=args.port, reload=False) if __name__ == "__main__": main()