"""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 <annan 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()