Enhance access link collection and backend status reporting
- Updated the `collect_access_links` function to provide clearer user-facing endpoint labels and notes, particularly for non-tunneled scenarios. - Improved logging messages in the provisioning process to reflect the status of the SwarmUI and Ollama API, enhancing user feedback during setup. - Added human-readable status messages for backend loading and running states, improving clarity during the waiting process. - Updated tests to verify the new behavior and ensure accurate reporting of access links and backend statuses.
This commit is contained in:
+52
-43
@@ -39,52 +39,60 @@ def resolve_llm_runtime(cfg: Config) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def collect_access_links(cfg: Config, *, tunneled: bool) -> list[AccessLink]:
|
def collect_access_links(cfg: Config, *, tunneled: bool) -> list[AccessLink]:
|
||||||
"""Build the list of user-facing endpoints (unit-tested)."""
|
"""Build the list of user-facing endpoints (unit-tested).
|
||||||
|
|
||||||
|
When ``tunneled`` is False, still lists the same localhost URLs with a
|
||||||
|
«после tunnel» note — useful mid-``up`` before SSH forward is up.
|
||||||
|
"""
|
||||||
links: list[AccessLink] = []
|
links: list[AccessLink] = []
|
||||||
swarm = bool(getattr(cfg, "enable_swarmui", True))
|
swarm = bool(getattr(cfg, "enable_swarmui", True))
|
||||||
if tunneled:
|
soon = "" if tunneled else "после tunnel"
|
||||||
if swarm:
|
if swarm:
|
||||||
port = cfg.swarmui_local_port
|
port = cfg.swarmui_local_port
|
||||||
base = f"http://127.0.0.1:{port}"
|
base = f"http://127.0.0.1:{port}"
|
||||||
links.extend(
|
links.extend(
|
||||||
[
|
[
|
||||||
AccessLink("SwarmUI UI", base, "браузер"),
|
AccessLink("SwarmUI UI", base, soon or "браузер"),
|
||||||
AccessLink("SwarmUI API", f"{base}/API/", "HTTP JSON"),
|
AccessLink("SwarmUI API", f"{base}/API/", soon or "HTTP JSON"),
|
||||||
AccessLink("SwarmUI MCP", f"{base}/mcp", "Cursor mcp.json"),
|
AccessLink("SwarmUI MCP", f"{base}/mcp", soon or "Cursor mcp.json"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
runtime = resolve_llm_runtime(cfg)
|
runtime = resolve_llm_runtime(cfg)
|
||||||
if runtime == "ollama":
|
if runtime == "ollama":
|
||||||
o = cfg.ollama_local_port
|
o = cfg.ollama_local_port
|
||||||
links.extend(
|
links.extend(
|
||||||
[
|
[
|
||||||
AccessLink(
|
|
||||||
"Ollama API",
|
|
||||||
f"http://127.0.0.1:{o}",
|
|
||||||
f"OLLAMA_HOST=http://127.0.0.1:{o}",
|
|
||||||
),
|
|
||||||
AccessLink("Ollama tags", f"http://127.0.0.1:{o}/api/tags", "список моделей"),
|
|
||||||
AccessLink(
|
|
||||||
"Ollama chat",
|
|
||||||
f"http://127.0.0.1:{o}/api/chat",
|
|
||||||
"POST generate",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if not links:
|
|
||||||
links.append(
|
|
||||||
AccessLink(
|
AccessLink(
|
||||||
"Туннель",
|
"Ollama API",
|
||||||
"gpu-rent tunnel",
|
f"http://127.0.0.1:{o}",
|
||||||
"нет сервисов — ENABLE_SWARMUI / LLM_RUNTIME",
|
soon or f"OLLAMA_HOST=http://127.0.0.1:{o}",
|
||||||
)
|
),
|
||||||
)
|
AccessLink(
|
||||||
else:
|
"Ollama tags",
|
||||||
|
f"http://127.0.0.1:{o}/api/tags",
|
||||||
|
soon or "список моделей",
|
||||||
|
),
|
||||||
|
AccessLink(
|
||||||
|
"Ollama chat",
|
||||||
|
f"http://127.0.0.1:{o}/api/chat",
|
||||||
|
soon or "POST generate",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if not links:
|
||||||
links.append(
|
links.append(
|
||||||
AccessLink(
|
AccessLink(
|
||||||
"Туннель",
|
"Туннель",
|
||||||
"gpu-rent tunnel --open",
|
"gpu-rent tunnel",
|
||||||
"локальные URL появятся после tunnel",
|
"нет сервисов — ENABLE_SWARMUI / LLM_RUNTIME",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif not tunneled:
|
||||||
|
links.append(
|
||||||
|
AccessLink(
|
||||||
|
"Сейчас",
|
||||||
|
"ждём Idle → туннель",
|
||||||
|
"не открывай :7801 на ноутбуке — только :17801 после tunnel",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return links
|
return links
|
||||||
@@ -139,7 +147,7 @@ def render_access_panel(
|
|||||||
if host:
|
if host:
|
||||||
subtitle.append(f" · VM {host}", style="dim")
|
subtitle.append(f" · VM {host}", style="dim")
|
||||||
else:
|
else:
|
||||||
subtitle.append("облако готово, туннеля нет", style="yellow")
|
subtitle.append("URL ниже — после Idle откроется туннель", style="yellow")
|
||||||
if host:
|
if host:
|
||||||
subtitle.append(f" · FIP {host}", style="dim")
|
subtitle.append(f" · FIP {host}", style="dim")
|
||||||
|
|
||||||
@@ -191,11 +199,12 @@ def print_access_card(
|
|||||||
*,
|
*,
|
||||||
tunneled: bool = True,
|
tunneled: bool = True,
|
||||||
host: str | None = None,
|
host: str | None = None,
|
||||||
|
title: str = "gpu-rent · доступы",
|
||||||
console: Console | None = None,
|
console: Console | None = None,
|
||||||
log: Log | None = None,
|
log: Log | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Print Rich panel; fall back to plain lines if needed."""
|
"""Print Rich panel; fall back to plain lines if needed."""
|
||||||
panel = render_access_panel(cfg, tunneled=tunneled, host=host)
|
panel = render_access_panel(cfg, tunneled=tunneled, host=host, title=title)
|
||||||
if console is not None:
|
if console is not None:
|
||||||
console.print()
|
console.print()
|
||||||
console.print(panel)
|
console.print(panel)
|
||||||
@@ -204,7 +213,7 @@ def print_access_card(
|
|||||||
if log is not None:
|
if log is not None:
|
||||||
# Plain fallback for non-Rich loggers
|
# Plain fallback for non-Rich loggers
|
||||||
log("")
|
log("")
|
||||||
log("══ gpu-rent · доступы ══")
|
log(f"══ {title} ══")
|
||||||
try:
|
try:
|
||||||
notes = load_state().notes or {}
|
notes = load_state().notes or {}
|
||||||
if notes.get("idle_killer") == "failed":
|
if notes.get("idle_killer") == "failed":
|
||||||
|
|||||||
@@ -627,7 +627,16 @@ def provision_vm(
|
|||||||
_try_arm_idle_killer(cfg, host, log, conn=conn, server_id=server_id)
|
_try_arm_idle_killer(cfg, host, log, conn=conn, server_id=server_id)
|
||||||
|
|
||||||
if swarm:
|
if swarm:
|
||||||
log("SwarmUI слушает 127.0.0.1:7801 — gpu-rent tunnel")
|
from gpu_rent.access_card import print_access_card
|
||||||
if rt == "ollama":
|
from gpu_rent.term import console as rich_console
|
||||||
|
|
||||||
|
print_access_card(
|
||||||
|
cfg,
|
||||||
|
tunneled=False,
|
||||||
|
host=host,
|
||||||
|
console=rich_console,
|
||||||
|
title="gpu-rent · URL после tunnel (ещё ждём Idle)",
|
||||||
|
)
|
||||||
|
elif rt == "ollama":
|
||||||
log(f"Ollama API → localhost:{cfg.ollama_local_port} (туннель)")
|
log(f"Ollama API → localhost:{cfg.ollama_local_port} (туннель)")
|
||||||
log("Hold killer: gpu-rent hold | Стоп GPU: gpu-rent stop")
|
log("Hold killer: gpu-rent hold | Стоп GPU: gpu-rent stop")
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ while time.time() < deadline:
|
|||||||
# No backends registered — Comfy never installed (Install wizard).
|
# No backends registered — Comfy never installed (Install wizard).
|
||||||
# Not "Idle": treat as wait, never ready.
|
# Not "Idle": treat as wait, never ready.
|
||||||
print("BUSY backend=empty (нужен first-install Comfy)")
|
print("BUSY backend=empty (нужен first-install Comfy)")
|
||||||
|
elif bstat == "loading":
|
||||||
|
print("BUSY backend=loading (Comfy стартует, ждём Idle)")
|
||||||
|
elif bstat == "running":
|
||||||
|
# Self-start often reports running while still warming / first load.
|
||||||
|
print("BUSY backend=running (Comfy прогрев, ждём Idle)")
|
||||||
elif bstat in ("disabled", "all_disabled"):
|
elif bstat in ("disabled", "all_disabled"):
|
||||||
print(f"BUSY backend={bstat}")
|
print(f"BUSY backend={bstat}")
|
||||||
elif bstat == "idle":
|
elif bstat == "idle":
|
||||||
@@ -158,7 +163,10 @@ def wait_backend_idle(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Block until SwarmUI on the VM reports Idle backend (or timeout)."""
|
"""Block until SwarmUI on the VM reports Idle backend (or timeout)."""
|
||||||
deadline = time.time() + timeout
|
deadline = time.time() + timeout
|
||||||
log("жду HTTP :7801 и Idle backend на VM…")
|
log(
|
||||||
|
"жду Idle backend на VM (loading/running — норма, Comfy прогревается; "
|
||||||
|
"ссылки :17801 — после ready + tunnel)"
|
||||||
|
)
|
||||||
last = ""
|
last = ""
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ def test_collect_links_swarm_and_ollama(monkeypatch):
|
|||||||
|
|
||||||
def test_collect_links_no_tunnel():
|
def test_collect_links_no_tunnel():
|
||||||
links = collect_access_links(_Cfg(), tunneled=False)
|
links = collect_access_links(_Cfg(), tunneled=False)
|
||||||
assert links[0].url.startswith("gpu-rent tunnel")
|
labels = [x.label for x in links]
|
||||||
|
assert "SwarmUI UI" in labels
|
||||||
|
assert "Ollama API" in labels
|
||||||
|
assert any("17801" in x.url for x in links)
|
||||||
|
assert any(x.note == "после tunnel" for x in links)
|
||||||
|
assert any(x.label == "Сейчас" for x in links)
|
||||||
|
|
||||||
|
|
||||||
def test_mcp_snippet_json():
|
def test_mcp_snippet_json():
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ def test_remote_poll_idle_is_ready():
|
|||||||
assert "READY backend=" in _REMOTE_POLL
|
assert "READY backend=" in _REMOTE_POLL
|
||||||
|
|
||||||
|
|
||||||
|
def test_remote_poll_loading_running_humanized():
|
||||||
|
assert "Comfy стартует" in _REMOTE_POLL
|
||||||
|
assert "Comfy прогрев" in _REMOTE_POLL
|
||||||
|
assert 'bstat == "loading"' in _REMOTE_POLL
|
||||||
|
assert 'bstat == "running"' in _REMOTE_POLL
|
||||||
|
|
||||||
|
|
||||||
def test_install_swarm_comfy_script_payload():
|
def test_install_swarm_comfy_script_payload():
|
||||||
from importlib.resources import files
|
from importlib.resources import files
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user