diff --git a/distributed.py b/distributed.py index 46df518..aee2359 100644 --- a/distributed.py +++ b/distributed.py @@ -1582,23 +1582,6 @@ async def check_file_endpoint(request): except Exception as e: return await handle_api_error(request, e, 500) -@server.PromptServer.instance.routes.get("/distributed/system_info") -async def system_info_endpoint(request): - """Return system information including platform details""" - import platform - - return web.json_response({ - "platform": { - "os_name": os.name, # 'nt' for Windows, 'posix' for Unix/Linux - "path_separator": os.sep, # '\\' on Windows, '/' on Linux - "system": platform.system(), # 'Windows', 'Linux', 'Darwin' - "machine": platform.machine(), # 'x86_64', 'AMD64', etc. - }, - "comfyui": { - # Can add ComfyUI-specific info here - "version": "distributed-extension", - } - }) @server.PromptServer.instance.routes.post("/distributed/job_complete") async def job_complete_endpoint(request): diff --git a/web/executionUtils.js b/web/executionUtils.js index 4c116cd..7c493c9 100644 --- a/web/executionUtils.js +++ b/web/executionUtils.js @@ -83,6 +83,38 @@ export async function executeParallelDistributed(extension, promptWrapper) { extension.log(`Pre-flight check: ${activeWorkers.length} of ${enabledWorkers.length} workers are active`, "debug"); + // Check if master host might be unreachable by workers (cloudflare tunnel down) + const masterHost = extension.config?.master?.host || ''; + const isCloudflareHost = /\.(trycloudflare\.com|cloudflare\.dev)$/i.test(masterHost); + + if (isCloudflareHost && activeWorkers.length > 0) { + // Try to verify if the cloudflare tunnel is actually up + try { + const testUrl = `${window.location.protocol}//${masterHost}/distributed/status`; + const response = await fetch(testUrl, { + method: 'GET', + mode: 'cors', + cache: 'no-cache', + signal: AbortSignal.timeout(3000) // 3 second timeout + }); + + if (!response.ok) { + throw new Error('Master not reachable'); + } + } catch (error) { + // Cloudflare tunnel appears to be down + extension.log(`Master host ${masterHost} is not reachable - cloudflare tunnel may be down`, "error"); + + if (extension.ui?.showCloudflareWarning) { + extension.ui.showCloudflareWarning(extension, masterHost); + } + + // Stop execution - workers won't be able to send results back + extension.log("Blocking execution - workers cannot reach master at cloudflare domain", "error"); + return null; // This will prevent the workflow from running + } + } + // Find all distributed nodes in the workflow const collectorNodes = findNodesByClass(promptWrapper.output, "DistributedCollector"); const upscaleNodes = findNodesByClass(promptWrapper.output, "UltimateSDUpscaleDistributed"); diff --git a/web/ui.js b/web/ui.js index bdbc566..9bc0411 100644 --- a/web/ui.js +++ b/web/ui.js @@ -344,6 +344,131 @@ export class DistributedUI { } } + showCloudflareWarning(extension, masterHost) { + // Remove any existing banner first + const existingBanner = document.getElementById('cloudflare-warning-banner'); + if (existingBanner) { + existingBanner.remove(); + } + + // Create warning banner + const banner = document.createElement('div'); + banner.id = 'cloudflare-warning-banner'; + banner.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + background: #ff9800; + color: #333; + padding: 8px 16px; + text-align: center; + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + `; + + const messageSpan = document.createElement('span'); + messageSpan.innerHTML = `Connection issue: Master address ${masterHost} is not reachable. The cloudflare tunnel may be offline.`; + messageSpan.style.fontSize = '13px'; + + const resetButton = document.createElement('button'); + resetButton.textContent = 'Reset Master Address'; + resetButton.style.cssText = ` + background: #333; + color: white; + border: none; + padding: 6px 14px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + font-size: 13px; + transition: background 0.2s; + `; + resetButton.onmouseover = () => resetButton.style.background = '#555'; + resetButton.onmouseout = () => resetButton.style.background = '#333'; + + const dismissButton = document.createElement('button'); + dismissButton.textContent = 'Dismiss'; + dismissButton.style.cssText = ` + background: transparent; + color: #333; + border: 1px solid #333; + padding: 6px 14px; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + transition: opacity 0.2s; + `; + dismissButton.onmouseover = () => dismissButton.style.opacity = '0.7'; + dismissButton.onmouseout = () => dismissButton.style.opacity = '1'; + + // Add click handlers + resetButton.onclick = async () => { + resetButton.disabled = true; + resetButton.textContent = 'Resetting...'; + + try { + // Save with empty host - this will trigger auto-detection + await extension.api.updateMaster({ + name: extension.config?.master?.name || "Master", + host: "" + }); + + // The API call above doesn't trigger auto-detection, so we need to do it + await extension.detectMasterIP(); + + // Reload config to get the new detected IP + await extension.loadConfig(); + + // Update UI if sidebar is open + if (extension.panelElement) { + const hostInput = document.getElementById('master-host'); + if (hostInput) { + hostInput.value = extension.config?.master?.host || ""; + } + } + + // Show success message + extension.app.extensionManager.toast.add({ + severity: "success", + summary: "Master Host Reset", + detail: `IP auto-detected: ${extension.config?.master?.host || 'detecting...'}`, + life: 4000 + }); + + banner.remove(); + } catch (error) { + resetButton.disabled = false; + resetButton.textContent = 'Reset Master Host'; + extension.log(`Failed to reset master host: ${error.message}`, "error"); + } + }; + + dismissButton.onclick = () => banner.remove(); + + // Assemble banner + banner.appendChild(messageSpan); + banner.appendChild(resetButton); + banner.appendChild(dismissButton); + + // Add to page + document.body.prepend(banner); + + // Auto-dismiss after 30 seconds + setTimeout(() => { + if (document.getElementById('cloudflare-warning-banner')) { + banner.style.transition = 'opacity 0.5s'; + banner.style.opacity = '0'; + setTimeout(() => banner.remove(), 500); + } + }, 30000); + } + updateStatusDot(workerId, color, title, pulsing = false) { const statusDot = document.getElementById(`status-${workerId}`); if (!statusDot) return;