mirror of
https://github.com/blw1138/cross-py-builder.git
synced 2026-09-07 21:41:09 -05:00
Add controller with web UI and retire CLI
- Add ctrl/ package: Flask app (waitress), SQLite job store, background scheduler that probes configured workers, dispatches via /upload and /checkout_git, fetches artifacts, and serves a single-page UI - Supporting API: /api/jobs (create/list/detail/cancel), /api/workers, /api/capabilities, artifact download, and SSE log stream - Workers keep the existing HTTP API; add /progress/<job_id> endpoint and per-step build tracking for live status - Retire agent_manager.py and the cross-py-builder CLI; controller UI is the primary frontend - Record design in DESIGN.md; add waitress dependency Note: /progress is exposed on the worker but the synchronous /upload and /checkout endpoints block until a build completes, so per-step worker progress is not yet relayed live in the UI. Live streaming needs an async-start worker endpoint as a follow-up.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import ctrl.settings as settings
|
||||
import requests
|
||||
|
||||
|
||||
def parse_worker(spec):
|
||||
"""'host:port' -> {'url': 'http://host:port', 'host': host, 'port': port}."""
|
||||
host, _, port = spec.strip().rpartition(":")
|
||||
if not host or not port.isdigit():
|
||||
raise ValueError(f"Invalid worker spec: {spec!r} (expected host:port)")
|
||||
return {"url": f"http://{host}:{port}", "host": host, "port": int(port)}
|
||||
|
||||
|
||||
def configured_workers():
|
||||
return [parse_worker(s) for s in settings.WORKERS]
|
||||
|
||||
|
||||
def probe_worker(url, timeout=None):
|
||||
"""Return a normalized worker status dict, or None if unreachable."""
|
||||
timeout = timeout or settings.WORKER_STATUS_TIMEOUT
|
||||
try:
|
||||
resp = requests.get(f"{url}/status", timeout=timeout)
|
||||
resp.raise_for_status()
|
||||
info = resp.json()
|
||||
except (requests.RequestException, ValueError):
|
||||
return None
|
||||
|
||||
info["url"] = url
|
||||
info["online"] = True
|
||||
return info
|
||||
|
||||
|
||||
def probe_all():
|
||||
workers = []
|
||||
for w in configured_workers():
|
||||
status = probe_worker(w["url"])
|
||||
if status:
|
||||
workers.append(status)
|
||||
else:
|
||||
workers.append({"url": w["url"], "host": w["host"], "port": w["port"], "online": False})
|
||||
return workers
|
||||
|
||||
|
||||
def matches(job, worker):
|
||||
if worker.get("status") != "ready":
|
||||
return False
|
||||
if job.get("os_req") and job["os_req"].lower() not in str(worker.get("os", "")).lower():
|
||||
return False
|
||||
if job.get("cpu_req") and job["cpu_req"].lower() not in str(worker.get("cpu", "")).lower():
|
||||
return False
|
||||
return True
|
||||
Reference in New Issue
Block a user