diff --git a/js/publisher.js b/js/publisher.js index 547c3ab..bff598f 100644 --- a/js/publisher.js +++ b/js/publisher.js @@ -1,16 +1,91 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; +// 检测ComfyUI服务端口的函数 +async function detectComfyUIPort(host) { + try { + const currentPort = window.location.port; + if (currentPort) { + console.log(`从当前页面URL获取端口: ${currentPort}`); + return parseInt(currentPort); + } + } catch (error) { + console.warn("无法从当前页面获取端口:", error); + } + + // 默认返回8188 + console.warn("无法检测到ComfyUI端口,使用默认端口8188"); + return 8188; +} + +// 获取本地IP地址的函数 +async function getLocalIPAddress() { + try { + // 尝试通过WebRTC获取本地IP地址 + const pc = new RTCPeerConnection({ + iceServers: [], + }); + + pc.createDataChannel(""); + const offer = await pc.createOffer(); + await pc.setLocalDescription(offer); + + return new Promise((resolve) => { + pc.onicecandidate = (event) => { + if (event.candidate) { + const ipMatch = event.candidate.candidate.match( + /([0-9]{1,3}(\.[0-9]{1,3}){3})/ + ); + if (ipMatch) { + const localIP = ipMatch[1]; + // 保留有效的内网IP地址 + if ( + localIP.startsWith("192.168.") || + localIP.startsWith("10.") || + localIP.startsWith("172.") || + localIP === "127.0.0.1" + ) { + pc.close(); + resolve(localIP); + } + } + } + }; + + // 超时处理 + setTimeout(() => { + pc.close(); + resolve("127.0.0.1"); // 如果获取失败,使用默认地址 + }, 3000); + }); + } catch (error) { + console.warn("无法获取本地IP地址,使用默认地址:", error); + return "127.0.0.1"; + } +} + // 本地存储的 key const COMFY_SERVER_REGISTRATION_KEY = "comfyui_server_registration"; -// ComfyUI服务器注册配置 -const DEFAULT_SERVER_CONFIG = { +// ComfyUI服务器注册配置 - 使用动态IP地址 +let DEFAULT_SERVER_CONFIG = { name: "comfyui_node_1", - http_url: "http://127.0.0.1:8188", + http_url: "http://127.0.0.1:8188", // 将在初始化时更新为实际IP workflow_service_host: "http://localhost:8000", }; +// 初始化默认配置 +async function initializeDefaultConfig() { + try { + const localIP = await getLocalIPAddress(); + const port = await detectComfyUIPort(localIP); + DEFAULT_SERVER_CONFIG.http_url = `http://${localIP}:${port}`; + console.log("自动获取本地IP地址和端口:", localIP, port); + } catch (error) { + console.warn("初始化IP地址和端口失败,使用默认地址:", error); + } +} + // 获取ComfyUI服务器注册配置 function getComfyServerConfig() { try { @@ -66,8 +141,7 @@ class ComfyServerManager { // 注册服务器到工作流服务 async registerServer() { if (!this.serverConfig.workflow_service_host) { - console.warn("未设置工作流服务地址,无法注册"); - return false; + return { success: false, error: "未设置工作流服务地址" }; } try { @@ -91,22 +165,26 @@ class ComfyServerManager { console.log("ComfyUI服务器注册成功:", result); this.isRegistered = true; this.startHeartbeat(); - return true; + return { success: true }; } else { const errorText = await response.text(); - console.error("ComfyUI服务器注册失败:", response.status, errorText); - return false; + const errorMessage = `HTTP ${response.status}: ${ + errorText || "服务器响应错误" + }`; + console.error("ComfyUI服务器注册失败:", errorMessage); + return { success: false, error: errorMessage }; } } catch (error) { - console.error("注册ComfyUI服务器时发生错误:", error); - return false; + const errorMessage = `网络错误: ${error.message}`; + console.error("注册ComfyUI服务器时发生错误:", errorMessage); + return { success: false, error: errorMessage }; } } // 注销服务器 async unregisterServer() { if (!this.serverConfig.workflow_service_host || !this.isRegistered) { - return false; + return { success: false, error: "未设置工作流服务地址或服务器未注册" }; } try { @@ -125,14 +203,19 @@ class ComfyServerManager { console.log("ComfyUI服务器注销成功"); this.isRegistered = false; this.stopHeartbeat(); - return true; + return { success: true }; } else { - console.error("ComfyUI服务器注销失败:", response.status); - return false; + const errorText = await response.text(); + const errorMessage = `HTTP ${response.status}: ${ + errorText || "服务器响应错误" + }`; + console.error("ComfyUI服务器注销失败:", errorMessage); + return { success: false, error: errorMessage }; } } catch (error) { - console.error("注销ComfyUI服务器时发生错误:", error); - return false; + const errorMessage = `网络错误: ${error.message}`; + console.error("注销ComfyUI服务器时发生错误:", errorMessage); + return { success: false, error: errorMessage }; } } @@ -206,8 +289,12 @@ class ComfyServerManager { // 如果已注册,需要重新注册以更新配置 if (this.isRegistered) { - this.unregisterServer().then(() => { - setTimeout(() => this.registerServer(), 1000); + this.unregisterServer().then((result) => { + if (result.success) { + setTimeout(() => this.registerServer(), 1000); + } else { + console.warn("更新配置时注销失败:", result.error); + } }); } } @@ -264,6 +351,46 @@ class ComfyServerManager { } return null; } + + // 动态更新IP地址和端口 + async updateLocalIPAddress() { + try { + const localIP = await getLocalIPAddress(); + const port = await detectComfyUIPort(localIP); + const newHttpUrl = `http://${localIP}:${port}`; + + if (this.serverConfig.http_url !== newHttpUrl) { + this.serverConfig.http_url = newHttpUrl; + saveComfyServerConfig(this.serverConfig); + console.log("已更新本地IP地址和端口:", localIP, port); + return { ip: localIP, port: port }; + } + return null; + } catch (error) { + console.warn("更新IP地址和端口失败:", error); + return null; + } + } + + // 同步注册状态 + syncRegistrationStatus() { + if (this.serverConfig.workflow_service_host) { + this.getServerStatus() + .then((status) => { + if (status) { + this.isRegistered = true; + console.log("同步注册状态: 已注册"); + } else { + this.isRegistered = false; + console.log("同步注册状态: 未注册"); + } + }) + .catch((error) => { + console.warn("同步注册状态失败:", error); + this.isRegistered = false; + }); + } + } } // 自定义 fetchApi 函数,使用ComfyUI服务器配置中的工作流服务地址 @@ -375,6 +502,9 @@ const createButton = (innerHTML, title, { onClick }) => { app.registerExtension({ name: "Comfy.WorkflowPublisher", async setup() { + // 初始化默认配置,获取本地IP地址 + await initializeDefaultConfig(); + const addCustomStyles = () => { const styleId = "jags-publisher-styles"; if (document.getElementById(styleId)) return; @@ -589,20 +719,31 @@ app.registerExtension({
- +
- - WebSocket地址将自动生成为: ${serverConfig.http_url ? serverConfig.http_url.replace(/^https?:\/\//, 'ws://') + '/ws' : ''} + + WebSocket地址将自动生成为: ${ + serverConfig.http_url + ? serverConfig.http_url.replace(/^https?:\/\//, "ws://") + + "/ws" + : "" + }
- +
@@ -613,6 +754,7 @@ app.registerExtension({ +
`; @@ -627,19 +769,22 @@ app.registerExtension({ const serverManager = new ComfyServerManager(); if (settings.host) { serverManager.setWorkflowServiceHost(settings.host); + // 同步注册状态 + serverManager.syncRegistrationStatus(); } // 绑定事件 const btnRegister = document.getElementById("btn-register"); const btnUnregister = document.getElementById("btn-unregister"); const btnRefreshStatus = document.getElementById("btn-refresh-status"); + const btnRefreshIP = document.getElementById("btn-refresh-ip"); const statusText = document.getElementById("status-text"); // 添加HTTP地址输入框的实时预览功能 const httpUrlInput = document.getElementById("server-http-url"); const wsUrlPreview = document.getElementById("ws-url-preview"); - - httpUrlInput.addEventListener("input", function() { + + httpUrlInput.addEventListener("input", function () { const httpUrl = this.value; if (httpUrl) { try { @@ -677,15 +822,16 @@ app.registerExtension({ btnRegister.disabled = true; btnRegister.textContent = "注册中..."; - const success = await serverManager.registerServer(); - if (success) { + const result = await serverManager.registerServer(); + if (result.success) { statusText.textContent = "已注册"; statusText.style.color = "var(--success-color)"; btnRegister.textContent = "已注册"; btnRegister.disabled = true; btnUnregister.disabled = false; } else { - statusText.textContent = "注册失败"; + const errorMessage = result.error || "未知错误"; + statusText.textContent = `注册失败: ${errorMessage}`; statusText.style.color = "var(--error-color)"; btnRegister.textContent = "注册服务器"; btnRegister.disabled = false; @@ -697,8 +843,8 @@ app.registerExtension({ btnUnregister.disabled = true; btnUnregister.textContent = "注销中..."; - const success = await serverManager.unregisterServer(); - if (success) { + const result = await serverManager.unregisterServer(); + if (result.success) { statusText.textContent = "已注销"; statusText.style.color = "orange"; btnRegister.disabled = false; @@ -706,7 +852,8 @@ app.registerExtension({ btnUnregister.disabled = true; btnUnregister.textContent = "注销服务器"; } else { - statusText.textContent = "注销失败"; + const errorMessage = result.error || "未知错误"; + statusText.textContent = `注销失败: ${errorMessage}`; statusText.style.color = "var(--error-color)"; btnUnregister.disabled = false; btnUnregister.textContent = "注销服务器"; @@ -718,17 +865,56 @@ app.registerExtension({ if (settings.host) { serverManager.setWorkflowServiceHost(settings.host); const status = await serverManager.getServerStatus(); + + // 调试信息 + console.log("服务器状态:", status); + if (status && status.last_heartbeat) { + console.log("最后心跳时间原始值:", status.last_heartbeat); + console.log("时间类型:", typeof status.last_heartbeat); + } + if (status) { - statusText.textContent = `在线 - 最后心跳: ${new Date( - status.last_heartbeat - ).toLocaleString()}`; + // 安全地处理时间显示 + let heartbeatTime = "未知"; + if (status.last_heartbeat) { + try { + // 尝试解析ISO格式的时间字符串 + const date = new Date(status.last_heartbeat); + if (!isNaN(date.getTime())) { + heartbeatTime = date.toLocaleString("zh-CN", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + } else { + heartbeatTime = "时间格式错误"; + } + } catch (error) { + console.warn( + "时间解析失败:", + error, + "原始时间:", + status.last_heartbeat + ); + heartbeatTime = "时间解析失败"; + } + } + + statusText.textContent = `在线 - 最后心跳: ${heartbeatTime}`; statusText.style.color = "var(--success-color)"; + // 同步更新本地注册状态 + serverManager.isRegistered = true; btnRegister.disabled = true; btnRegister.textContent = "已注册"; btnUnregister.disabled = false; } else { statusText.textContent = "未注册"; statusText.style.color = "orange"; + // 同步更新本地注册状态 + serverManager.isRegistered = false; btnRegister.disabled = false; btnRegister.textContent = "注册服务器"; btnUnregister.disabled = true; @@ -736,10 +922,81 @@ app.registerExtension({ } }; + // 刷新IP地址和端口 + btnRefreshIP.onclick = async () => { + btnRefreshIP.disabled = true; + btnRefreshIP.textContent = "刷新中..."; + + try { + const result = await serverManager.updateLocalIPAddress(); + if (result) { + // 更新输入框显示 + document.getElementById( + "server-http-url" + ).value = `http://${result.ip}:${result.port}`; + // 更新WebSocket预览 + const wsUrlPreview = document.getElementById("ws-url-preview"); + wsUrlPreview.textContent = `ws://${result.ip}:${result.port}/ws`; + + statusText.textContent = `IP地址和端口已更新为: ${result.ip}:${result.port}`; + statusText.style.color = "var(--success-color)"; + } else { + statusText.textContent = "IP地址和端口无需更新"; + statusText.style.color = "orange"; + } + } catch (error) { + statusText.textContent = `IP地址和端口更新失败: ${error.message}`; + statusText.style.color = "var(--error-color)"; + } finally { + btnRefreshIP.disabled = false; + btnRefreshIP.textContent = "刷新IP和端口"; + } + }; + // 初始状态检查 if (settings.host) { btnRefreshStatus.click(); + } else { + // 如果没有设置工作流服务地址,确保按钮状态正确 + btnRegister.disabled = false; + btnUnregister.disabled = true; } + + // 自动刷新IP地址和端口 + setTimeout(async () => { + try { + const result = await serverManager.updateLocalIPAddress(); + if (result) { + // 更新输入框显示 + document.getElementById( + "server-http-url" + ).value = `http://${result.ip}:${result.port}`; + // 更新WebSocket预览 + const wsUrlPreview = document.getElementById("ws-url-preview"); + wsUrlPreview.textContent = `ws://${result.ip}:${result.port}/ws`; + console.log( + "对话框显示时自动更新IP地址和端口:", + result.ip, + result.port + ); + } + } catch (error) { + console.warn("自动更新IP地址和端口失败:", error); + } + }, 500); + + // 延迟同步按钮状态,确保与内部状态一致 + setTimeout(() => { + if (serverManager.isRegistered) { + btnRegister.disabled = true; + btnRegister.textContent = "已注册"; + btnUnregister.disabled = false; + } else { + btnRegister.disabled = false; + btnRegister.textContent = "注册服务器"; + btnUnregister.disabled = true; + } + }, 1000); } catch (error) { console.error("显示ComfyUI服务器管理对话框失败:", error); alert(`显示对话框失败: ${error.message}`); diff --git a/workflow_service/comfy/comfy_server.py b/workflow_service/comfy/comfy_server.py index a7e7886..6d060b2 100644 --- a/workflow_service/comfy/comfy_server.py +++ b/workflow_service/comfy/comfy_server.py @@ -194,6 +194,33 @@ class ComfyUIServerManager: await self._ensure_health_check_started() async with self.lock: + # 解析URL获取IP和端口 + from urllib.parse import urlparse + + parsed_url = urlparse(http_url) + host = parsed_url.hostname + port = parsed_url.port or (80 if parsed_url.scheme == 'http' else 443) + + # 检查是否已存在相同IP+端口的服务器 + existing_server_name = None + for existing_server in self.servers.values(): + existing_parsed = urlparse(existing_server.http_url) + existing_host = existing_parsed.hostname + existing_port = existing_parsed.port or (80 if existing_parsed.scheme == 'http' else 443) + + if existing_host == host and existing_port == port: + existing_server_name = existing_server.name + logger.info(f"检测到IP {host}:{port} 已被服务器 {existing_server_name} 使用,将自动更新配置") + break + + # 如果IP+端口已存在,更新现有服务器 + if existing_server_name: + # 删除旧的服务器记录 + if existing_server_name in self.servers: + del self.servers[existing_server_name] + logger.info(f"已删除旧服务器 {existing_server_name} 的记录") + + # 检查名称是否已存在 if name in self.servers: logger.warning(f"服务器名称 {name} 已存在,将更新配置") @@ -217,8 +244,12 @@ class ComfyUIServerManager: # 保存到数据库 await self._save_server_to_db(server_info) + + # 如果更新了现有服务器,从数据库删除旧记录 + if existing_server_name: + await self._delete_server_from_db(existing_server_name) - logger.info(f"服务器 {name} 注册成功: {http_url}") + logger.info(f"服务器 {name} 注册成功: {http_url} (IP: {host}:{port})") return True async def unregister_server(self, name: str) -> bool: @@ -393,7 +424,8 @@ class ComfyUIServerManager: if is_healthy and server.status != ServerStatus.ONLINE: server.status = ServerStatus.ONLINE - logger.info(f"服务器 {server.name} 恢复在线") + server.last_heartbeat = current_time # 同步更新心跳时间 + logger.info(f"服务器 {server.name} 恢复在线,心跳时间已同步") elif not is_healthy and server.status == ServerStatus.ONLINE: server.status = ServerStatus.ERROR logger.warning(f"服务器 {server.name} 健康检查失败") diff --git a/workflow_service/routes/monitor.py b/workflow_service/routes/monitor.py index 168d280..85cc0b7 100644 --- a/workflow_service/routes/monitor.py +++ b/workflow_service/routes/monitor.py @@ -11,7 +11,10 @@ import json logger = logging.getLogger(__name__) from workflow_service.config import settings -from workflow_service.database.api import get_workflow_runs_recent, get_workflow_run_nodes +from workflow_service.database.api import ( + get_workflow_runs_recent, + get_workflow_run_nodes, +) from workflow_service.comfy.comfy_queue import WorkflowQueueManager from workflow_service.comfy.comfy_server import server_manager @@ -20,6 +23,7 @@ monitor_router = APIRouter(prefix="/monitor", tags=["监控"]) # 全局队列管理器实例 queue_manager = WorkflowQueueManager() + @monitor_router.get("/", response_class=HTMLResponse) async def monitor_dashboard(): """监控仪表板页面""" @@ -139,8 +143,8 @@ async def monitor_dashboard(): .server-card { border: 1px solid #e1e8ed; - border-radius: 12px; - padding: 20px; + border-radius: 8px; + padding: 12px; background: #f8f9fa; transition: all 0.3s ease; position: relative; @@ -175,34 +179,84 @@ async def monitor_dashboard(): background: #e74c3c; } + .server-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; + } + .server-card h4 { color: #2c3e50; - margin-bottom: 12px; - font-size: 1.1em; + margin: 0; + font-size: 1em; font-weight: 600; + white-space: nowrap; + } + + .server-url { + color: #3498db; + font-size: 0.8em; + font-weight: 500; + font-family: monospace; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; } .server-info { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; - font-size: 0.9em; + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + font-size: 0.8em; + margin-top: 8px; } .info-item { display: flex; align-items: center; - gap: 6px; + gap: 4px; + white-space: nowrap; + } + + .info-separator { + color: #e1e8ed; + margin: 0 4px; + } + + .status-icon { + font-size: 0.9em; + } + + .status-text { + font-weight: 600; + color: #2c3e50; } .info-label { color: #7f8c8d; font-weight: 500; + font-size: 0.75em; } .info-value { color: #2c3e50; font-weight: 600; + font-size: 0.75em; + } + + .info-value:has(span[style*="color: #e74c3c"]) { + color: #e74c3c; + } + + .info-value:has(span[style*="color: #f39c12"]) { + color: #f39c12; + } + + .info-value:has(span[style*="color: #27ae60"]) { + color: #27ae60; } .status-indicator { @@ -376,26 +430,46 @@ async def monitor_dashboard(): } }; + // 格式化心跳时间,突出显示状态 + const formatHeartbeat = (timeStr) => { + if (!timeStr) return 'N/A'; + try { + const date = new Date(timeStr); + const now = new Date(); + const diff = now - date; + + if (diff > 86400000) return '⚠️ 心跳超时'; // 超过1天 + if (diff > 3600000) return '⚠️ 心跳延迟'; // 超过1小时 + if (diff < 60000) return '✅ 刚刚'; + if (diff < 3600000) return `✅ ${Math.floor(diff / 60000)}分钟前`; + return `✅ ${Math.floor(diff / 3600000)}小时前`; + } catch { + return timeStr; + } + }; + container.innerHTML += `
-

${server.http_url}

+
+

${server.name || '未命名'}

+ 📍 ${server.http_url} +
- - 状态: - ${server.status === 'online' ? '在线' : '离线'} + ${server.status === 'online' ? '🟢' : '🔴'} + ${server.status === 'online' ? '在线' : '离线'}
+ |
- 任务: - ${server.current_tasks || 0}/${server.max_concurrent_tasks || 1} + 任务: ${server.current_tasks || 0}/${server.max_concurrent_tasks || 1}
+ |
- 心跳: - ${formatTime(server.last_heartbeat)} + 心跳: ${formatHeartbeat(server.last_heartbeat)}
+ |
- 检查: - ${formatTime(server.last_health_check)} + 检查: ${formatTime(server.last_health_check)}
${server.error ? `
${server.error}
` : ''} @@ -448,14 +522,15 @@ async def monitor_dashboard(): """ return HTMLResponse(content=html_content) + @monitor_router.get("/system-stats") async def get_system_stats() -> Dict[str, Any]: """获取系统统计信息""" try: cpu_percent = psutil.cpu_percent(interval=1) memory = psutil.virtual_memory() - disk = psutil.disk_usage('/') - + disk = psutil.disk_usage("/") + return { "cpu_percent": round(cpu_percent, 1), "memory_percent": round(memory.percent, 1), @@ -464,11 +539,12 @@ async def get_system_stats() -> Dict[str, Any]: "disk_percent": round(disk.percent, 1), "disk_used_gb": round(disk.used / (1024**3), 2), "disk_total_gb": round(disk.total / (1024**3), 2), - "timestamp": datetime.now().isoformat() + "timestamp": datetime.now().isoformat(), } except Exception as e: raise HTTPException(status_code=500, detail=f"获取系统统计信息失败: {str(e)}") + @monitor_router.get("/task-stats") async def get_task_stats() -> Dict[str, Any]: """获取任务统计信息""" @@ -476,139 +552,164 @@ async def get_task_stats() -> Dict[str, Any]: # 获取最近24小时的任务统计 end_time = datetime.now() start_time = end_time - timedelta(hours=24) - + recent_runs = await get_workflow_runs_recent(start_time, end_time) - + # 统计各种状态的任务数量 status_counts = {} for run in recent_runs: - status = run.get('status', 'unknown') + status = run.get("status", "unknown") status_counts[status] = status_counts.get(status, 0) + 1 - + return { - "running_tasks": status_counts.get('running', 0), - "pending_tasks": status_counts.get('pending', 0), - "completed_tasks": status_counts.get('completed', 0), - "failed_tasks": status_counts.get('failed', 0), + "running_tasks": status_counts.get("running", 0), + "pending_tasks": status_counts.get("pending", 0), + "completed_tasks": status_counts.get("completed", 0), + "failed_tasks": status_counts.get("failed", 0), "total_tasks_24h": len(recent_runs), - "timestamp": datetime.now().isoformat() + "timestamp": datetime.now().isoformat(), } except Exception as e: raise HTTPException(status_code=500, detail=f"获取任务统计信息失败: {str(e)}") + @monitor_router.get("/server-status") async def get_server_status() -> List[Dict[str, Any]]: """获取服务器状态信息""" try: import aiohttp import asyncio - + # 从 ComfyUIServerManager 获取所有注册的服务器 all_servers = await server_manager.get_all_servers() - + if not all_servers: logger.info("当前没有动态注册的服务器") return [] - + async def check_server_status(server): try: timeout = aiohttp.ClientTimeout(total=5) async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.get(f"{server.http_url}/system_stats") as response: + async with session.get( + f"{server.http_url}/system_stats" + ) as response: if response.status == 200: return { + "name": server.name, "http_url": server.http_url, "ws_url": server.ws_url, "status": "online", - "response_time": response.headers.get('X-Response-Time', 'N/A'), - "current_tasks": getattr(server, 'current_tasks', 0), - "max_concurrent_tasks": getattr(server, 'max_concurrent_tasks', 1), - "last_heartbeat": getattr(server, 'last_heartbeat', None), - "last_health_check": getattr(server, 'last_health_check', None) + "response_time": response.headers.get( + "X-Response-Time", "N/A" + ), + "current_tasks": getattr(server, "current_tasks", 0), + "max_concurrent_tasks": getattr( + server, "max_concurrent_tasks", 1 + ), + "last_heartbeat": getattr( + server, "last_heartbeat", None + ), + "last_health_check": getattr( + server, "last_health_check", None + ), } else: return { + "name": server.name, "http_url": server.http_url, "ws_url": server.ws_url, "status": "offline", "error": f"HTTP {response.status}", - "current_tasks": getattr(server, 'current_tasks', 0), - "max_concurrent_tasks": getattr(server, 'max_concurrent_tasks', 1), - "last_heartbeat": getattr(server, 'last_heartbeat', None), - "last_health_check": getattr(server, 'last_health_check', None) + "current_tasks": getattr(server, "current_tasks", 0), + "max_concurrent_tasks": getattr( + server, "max_concurrent_tasks", 1 + ), + "last_heartbeat": getattr( + server, "last_heartbeat", None + ), + "last_health_check": getattr( + server, "last_health_check", None + ), } except Exception as e: return { + "name": server.name, "http_url": server.http_url, "ws_url": server.ws_url, "status": "offline", "error": str(e), - "current_tasks": getattr(server, 'current_tasks', 0), - "max_concurrent_tasks": getattr(server, 'max_concurrent_tasks', 1), - "last_heartbeat": getattr(server, 'last_heartbeat', None), - "last_health_check": getattr(server, 'last_health_check', None) + "current_tasks": getattr(server, "current_tasks", 0), + "max_concurrent_tasks": getattr(server, "max_concurrent_tasks", 1), + "last_heartbeat": getattr(server, "last_heartbeat", None), + "last_health_check": getattr(server, "last_health_check", None), } - + # 并发检查所有服务器状态 tasks = [check_server_status(server) for server in all_servers] results = await asyncio.gather(*tasks, return_exceptions=True) - + # 过滤掉异常结果 valid_results = [] for result in results: if isinstance(result, dict): valid_results.append(result) - + return valid_results - + except Exception as e: raise HTTPException(status_code=500, detail=f"获取服务器状态失败: {str(e)}") + @monitor_router.get("/recent-tasks") async def get_recent_tasks(limit: int = 10) -> List[Dict[str, Any]]: """获取最近的任务列表""" try: end_time = datetime.now() start_time = end_time - timedelta(hours=24) - + recent_runs = await get_workflow_runs_recent(start_time, end_time) - + # 限制返回数量 limited_runs = recent_runs[:limit] - + # 格式化返回数据 formatted_runs = [] for run in limited_runs: - formatted_runs.append({ - "id": run.get('id'), # 使用数据库中的id字段 - "workflow_name": run.get('workflow_name'), - "status": run.get('status', 'unknown'), - "created_at": run.get('created_at'), - "updated_at": run.get('updated_at'), - "api_spec": run.get('api_spec') - }) - + formatted_runs.append( + { + "id": run.get("id"), # 使用数据库中的id字段 + "workflow_name": run.get("workflow_name"), + "status": run.get("status", "unknown"), + "created_at": run.get("created_at"), + "updated_at": run.get("updated_at"), + "api_spec": run.get("api_spec"), + } + ) + return formatted_runs - + except Exception as e: raise HTTPException(status_code=500, detail=f"获取最近任务失败: {str(e)}") + @monitor_router.get("/health") async def health_check() -> Dict[str, Any]: """健康检查端点""" try: # 检查数据库连接 from workflow_service.database.connection import get_db + db = get_db() - + # 从 ComfyUIServerManager 获取服务器信息 all_servers = await server_manager.get_all_servers() dynamic_servers_count = len(all_servers) - + # 检查服务器健康状态 - online_servers = [s for s in all_servers if s.status == 'online'] - offline_servers = [s for s in all_servers if s.status != 'online'] - + online_servers = [s for s in all_servers if s.status == "online"] + offline_servers = [s for s in all_servers if s.status != "online"] + return { "status": "healthy", "timestamp": datetime.now().isoformat(), @@ -617,17 +718,18 @@ async def health_check() -> Dict[str, Any]: "servers": { "total_dynamic": dynamic_servers_count, "online": len(online_servers), - "offline": len(offline_servers) + "offline": len(offline_servers), }, - "uptime": "N/A" # 可以添加启动时间跟踪 + "uptime": "N/A", # 可以添加启动时间跟踪 } except Exception as e: return { "status": "unhealthy", "timestamp": datetime.now().isoformat(), - "error": str(e) + "error": str(e), } + @monitor_router.get("/queue/status") async def get_queue_status(): """获取队列状态""" @@ -636,12 +738,13 @@ async def get_queue_status(): return { "success": True, "data": status, - "timestamp": datetime.now().isoformat() + "timestamp": datetime.now().isoformat(), } except Exception as e: logger.error(f"获取队列状态失败: {e}") raise HTTPException(status_code=500, detail=f"获取队列状态失败: {str(e)}") + @monitor_router.post("/queue/trigger") async def trigger_queue_processing(): """手动触发队列处理""" @@ -650,24 +753,25 @@ async def trigger_queue_processing(): return { "success": True, "message": "队列处理已手动触发", - "timestamp": datetime.now().isoformat() + "timestamp": datetime.now().isoformat(), } except Exception as e: logger.error(f"手动触发队列处理失败: {e}") raise HTTPException(status_code=500, detail=f"手动触发队列处理失败: {str(e)}") + @monitor_router.post("/queue/interval") async def set_monitor_interval(interval: int): """设置队列监控间隔""" try: if interval < 1: raise HTTPException(status_code=400, detail="监控间隔不能小于1秒") - + await queue_manager.set_monitor_interval(interval) return { "success": True, "message": f"队列监控间隔已设置为 {interval} 秒", - "timestamp": datetime.now().isoformat() + "timestamp": datetime.now().isoformat(), } except Exception as e: logger.error(f"设置监控间隔失败: {e}")