refactor: 移除心跳机制相关代码,简化服务器管理逻辑,更新文档以反映最新功能和配置
This commit is contained in:
@@ -49,7 +49,6 @@
|
|||||||
http_url TEXT NOT NULL,
|
http_url TEXT NOT NULL,
|
||||||
ws_url TEXT NOT NULL,
|
ws_url TEXT NOT NULL,
|
||||||
status TEXT NOT NULL DEFAULT 'offline',
|
status TEXT NOT NULL DEFAULT 'offline',
|
||||||
last_heartbeat DATETIME,
|
|
||||||
last_health_check DATETIME,
|
last_health_check DATETIME,
|
||||||
current_tasks INTEGER DEFAULT 0,
|
current_tasks INTEGER DEFAULT 0,
|
||||||
max_concurrent_tasks INTEGER DEFAULT 1,
|
max_concurrent_tasks INTEGER DEFAULT 1,
|
||||||
@@ -127,7 +126,6 @@ ComfyUI 服务器管理器现在支持持久化功能,这意味着即使管理
|
|||||||
- **自动持久化**: 服务器注册、状态变化、注销时自动保存到数据库
|
- **自动持久化**: 服务器注册、状态变化、注销时自动保存到数据库
|
||||||
- **自动恢复**: 服务启动时自动从数据库加载已注册的服务器
|
- **自动恢复**: 服务启动时自动从数据库加载已注册的服务器
|
||||||
- **健康检查**: 定期检查服务器健康状态并更新数据库
|
- **健康检查**: 定期检查服务器健康状态并更新数据库
|
||||||
- **心跳机制**: 支持服务器主动发送心跳,自动检测超时
|
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
|
|||||||
746
js/publisher.js
746
js/publisher.js
@@ -1,413 +1,44 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.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
|
// 本地存储的 key
|
||||||
const COMFY_SERVER_REGISTRATION_KEY = "comfyui_server_registration";
|
const WORKFLOW_SERVICE_HOST_KEY = "workflow_publisher_service_host";
|
||||||
|
|
||||||
// ComfyUI服务器注册配置 - 使用动态IP地址
|
// 默认工作流服务地址
|
||||||
let DEFAULT_SERVER_CONFIG = {
|
const DEFAULT_WORKFLOW_SERVICE_HOST = "http://localhost:8000";
|
||||||
name: "comfyui_node_1",
|
|
||||||
http_url: "http://127.0.0.1:8188", // 将在初始化时更新为实际IP
|
|
||||||
workflow_service_host: "http://localhost:8000",
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化默认配置
|
// 获取工作流服务地址
|
||||||
async function initializeDefaultConfig() {
|
function getWorkflowServiceHost() {
|
||||||
try {
|
try {
|
||||||
const localIP = await getLocalIPAddress();
|
const stored = localStorage.getItem(WORKFLOW_SERVICE_HOST_KEY);
|
||||||
const port = await detectComfyUIPort(localIP);
|
return stored || DEFAULT_WORKFLOW_SERVICE_HOST;
|
||||||
DEFAULT_SERVER_CONFIG.http_url = `http://${localIP}:${port}`;
|
|
||||||
console.log("自动获取本地IP地址和端口:", localIP, port);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("初始化IP地址和端口失败,使用默认地址:", error);
|
console.error("读取工作流服务地址失败:", error);
|
||||||
|
return DEFAULT_WORKFLOW_SERVICE_HOST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取ComfyUI服务器注册配置
|
// 保存工作流服务地址
|
||||||
function getComfyServerConfig() {
|
function saveWorkflowServiceHost(host) {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(COMFY_SERVER_REGISTRATION_KEY);
|
localStorage.setItem(WORKFLOW_SERVICE_HOST_KEY, host);
|
||||||
return stored ? JSON.parse(stored) : { ...DEFAULT_SERVER_CONFIG };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("读取ComfyUI服务器配置失败:", error);
|
console.error("保存工作流服务地址失败:", error);
|
||||||
return { ...DEFAULT_SERVER_CONFIG };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存ComfyUI服务器注册配置
|
// 自定义 fetchApi 函数,使用配置的工作流服务地址
|
||||||
function saveComfyServerConfig(config) {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(COMFY_SERVER_REGISTRATION_KEY, JSON.stringify(config));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("保存ComfyUI服务器配置失败:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取本地设置
|
|
||||||
function getLocalSettings() {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem("workflow_publisher_settings");
|
|
||||||
return stored ? JSON.parse(stored) : { host: "" };
|
|
||||||
} catch (error) {
|
|
||||||
console.error("读取本地设置失败:", error);
|
|
||||||
return { host: "" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存本地设置
|
|
||||||
function saveLocalSettings(settings) {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(
|
|
||||||
"workflow_publisher_settings",
|
|
||||||
JSON.stringify(settings)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("保存本地设置失败:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComfyUI服务器注册管理器
|
|
||||||
class ComfyServerManager {
|
|
||||||
constructor() {
|
|
||||||
this.registrationInterval = null;
|
|
||||||
this.heartbeatInterval = null;
|
|
||||||
this.isRegistered = false;
|
|
||||||
this.serverConfig = getComfyServerConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册服务器到工作流服务
|
|
||||||
async registerServer() {
|
|
||||||
if (!this.serverConfig.workflow_service_host) {
|
|
||||||
return { success: false, error: "未设置工作流服务地址" };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.serverConfig.workflow_service_host}/api/comfy/register`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: this.serverConfig.name,
|
|
||||||
http_url: this.serverConfig.http_url,
|
|
||||||
ws_url: this.getWebSocketUrl(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const result = await response.json();
|
|
||||||
console.log("ComfyUI服务器注册成功:", result);
|
|
||||||
this.isRegistered = true;
|
|
||||||
this.startHeartbeat();
|
|
||||||
return { success: true };
|
|
||||||
} else {
|
|
||||||
const errorText = await response.text();
|
|
||||||
const errorMessage = `HTTP ${response.status}: ${
|
|
||||||
errorText || "服务器响应错误"
|
|
||||||
}`;
|
|
||||||
console.error("ComfyUI服务器注册失败:", errorMessage);
|
|
||||||
return { success: false, error: errorMessage };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = `网络错误: ${error.message}`;
|
|
||||||
console.error("注册ComfyUI服务器时发生错误:", errorMessage);
|
|
||||||
return { success: false, error: errorMessage };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注销服务器
|
|
||||||
async unregisterServer() {
|
|
||||||
if (!this.serverConfig.workflow_service_host || !this.isRegistered) {
|
|
||||||
return { success: false, error: "未设置工作流服务地址或服务器未注册" };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.serverConfig.workflow_service_host}/api/comfy/unregister`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ name: this.serverConfig.name }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
console.log("ComfyUI服务器注销成功");
|
|
||||||
this.isRegistered = false;
|
|
||||||
this.stopHeartbeat();
|
|
||||||
return { success: true };
|
|
||||||
} else {
|
|
||||||
const errorText = await response.text();
|
|
||||||
const errorMessage = `HTTP ${response.status}: ${
|
|
||||||
errorText || "服务器响应错误"
|
|
||||||
}`;
|
|
||||||
console.error("ComfyUI服务器注销失败:", errorMessage);
|
|
||||||
return { success: false, error: errorMessage };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = `网络错误: ${error.message}`;
|
|
||||||
console.error("注销ComfyUI服务器时发生错误:", errorMessage);
|
|
||||||
return { success: false, error: errorMessage };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送心跳
|
|
||||||
async sendHeartbeat() {
|
|
||||||
if (!this.serverConfig.workflow_service_host || !this.isRegistered) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.serverConfig.workflow_service_host}/api/comfy/heartbeat`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ name: this.serverConfig.name }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
console.debug("心跳发送成功");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
console.warn("心跳发送失败:", response.status);
|
|
||||||
// 如果心跳失败,尝试重新注册
|
|
||||||
if (response.status === 404) {
|
|
||||||
console.log("服务器不存在,尝试重新注册...");
|
|
||||||
this.isRegistered = false;
|
|
||||||
await this.registerServer();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("发送心跳时发生错误:", error);
|
|
||||||
// 如果网络错误,尝试重新注册
|
|
||||||
this.isRegistered = false;
|
|
||||||
await this.registerServer();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动心跳
|
|
||||||
startHeartbeat() {
|
|
||||||
if (this.heartbeatInterval) {
|
|
||||||
clearInterval(this.heartbeatInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 每30秒发送一次心跳
|
|
||||||
this.heartbeatInterval = setInterval(() => {
|
|
||||||
this.sendHeartbeat();
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
console.log("ComfyUI服务器心跳已启动");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止心跳
|
|
||||||
stopHeartbeat() {
|
|
||||||
if (this.heartbeatInterval) {
|
|
||||||
clearInterval(this.heartbeatInterval);
|
|
||||||
this.heartbeatInterval = null;
|
|
||||||
}
|
|
||||||
console.log("ComfyUI服务器心跳已停止");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新服务器配置
|
|
||||||
updateConfig(newConfig) {
|
|
||||||
this.serverConfig = { ...this.serverConfig, ...newConfig };
|
|
||||||
saveComfyServerConfig(this.serverConfig);
|
|
||||||
|
|
||||||
// 如果已注册,需要重新注册以更新配置
|
|
||||||
if (this.isRegistered) {
|
|
||||||
this.unregisterServer().then((result) => {
|
|
||||||
if (result.success) {
|
|
||||||
setTimeout(() => this.registerServer(), 1000);
|
|
||||||
} else {
|
|
||||||
console.warn("更新配置时注销失败:", result.error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置工作流服务地址
|
|
||||||
setWorkflowServiceHost(host) {
|
|
||||||
this.serverConfig.workflow_service_host = host;
|
|
||||||
saveComfyServerConfig(this.serverConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取WebSocket URL(自动从HTTP URL生成)
|
|
||||||
getWebSocketUrl() {
|
|
||||||
if (!this.serverConfig.http_url) return "";
|
|
||||||
const url = new URL(this.serverConfig.http_url);
|
|
||||||
// 根据协议自动选择ws或wss
|
|
||||||
const protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
||||||
return `${protocol}//${url.host}/ws`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取服务器状态
|
|
||||||
async getServerStatus() {
|
|
||||||
if (!this.serverConfig.workflow_service_host) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.serverConfig.workflow_service_host}/api/comfy/status/${this.serverConfig.name}`
|
|
||||||
);
|
|
||||||
if (response.ok) {
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("获取服务器状态失败:", error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取系统健康状态
|
|
||||||
async getSystemHealth() {
|
|
||||||
if (!this.serverConfig.workflow_service_host) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.serverConfig.workflow_service_host}/api/comfy/health`
|
|
||||||
);
|
|
||||||
if (response.ok) {
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("获取系统健康状态失败:", error);
|
|
||||||
}
|
|
||||||
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服务器配置中的工作流服务地址
|
|
||||||
async function customFetchApi(endpoint, options = {}) {
|
async function customFetchApi(endpoint, options = {}) {
|
||||||
try {
|
try {
|
||||||
// 从ComfyUI服务器配置获取工作流服务地址
|
const host = getWorkflowServiceHost();
|
||||||
const serverConfig = getComfyServerConfig();
|
if (!host) {
|
||||||
|
|
||||||
// 如果没有配置工作流服务地址,回退到默认的 api.fetchApi
|
|
||||||
if (!serverConfig.workflow_service_host) {
|
|
||||||
console.warn("未配置工作流服务地址,使用默认的 ComfyUI API");
|
console.warn("未配置工作流服务地址,使用默认的 ComfyUI API");
|
||||||
return await api.fetchApi(endpoint, options);
|
return await api.fetchApi(endpoint, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建完整的 URL
|
// 构建完整的 URL
|
||||||
const host = serverConfig.workflow_service_host.replace(/\/$/, ""); // 移除末尾的斜杠
|
const fullHost = host.replace(/\/$/, ""); // 移除末尾的斜杠
|
||||||
const fullUrl = `${host}${endpoint}`;
|
const fullUrl = `${fullHost}${endpoint}`;
|
||||||
|
|
||||||
console.log(`使用工作流服务地址请求: ${fullUrl}`);
|
console.log(`使用工作流服务地址请求: ${fullUrl}`);
|
||||||
|
|
||||||
@@ -457,7 +88,7 @@ function createElement(tagName, props) {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modal class (保持不变)
|
// Modal class
|
||||||
class Modal {
|
class Modal {
|
||||||
constructor(id, title, full_width = false) {
|
constructor(id, title, full_width = false) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -502,9 +133,6 @@ const createButton = (innerHTML, title, { onClick }) => {
|
|||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "Comfy.WorkflowPublisher",
|
name: "Comfy.WorkflowPublisher",
|
||||||
async setup() {
|
async setup() {
|
||||||
// 初始化默认配置,获取本地IP地址
|
|
||||||
await initializeDefaultConfig();
|
|
||||||
|
|
||||||
const addCustomStyles = () => {
|
const addCustomStyles = () => {
|
||||||
const styleId = "jags-publisher-styles";
|
const styleId = "jags-publisher-styles";
|
||||||
if (document.getElementById(styleId)) return;
|
if (document.getElementById(styleId)) return;
|
||||||
@@ -552,21 +180,22 @@ app.registerExtension({
|
|||||||
|
|
||||||
const iconGet = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>`;
|
const iconGet = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>`;
|
||||||
const iconPublish = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" /><path d="M7 9l5-5 5 5" /><path d="M12 4v12" /></svg>`;
|
const iconPublish = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" /><path d="M7 9l5-5 5 5" /><path d="M12 4v12" /></svg>`;
|
||||||
|
const iconSettings = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1 1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`;
|
||||||
|
|
||||||
const btnGet = createButton(iconGet, "从服务器获取工作流", {
|
const btnGet = createButton(iconGet, "从服务器获取工作流", {
|
||||||
onClick: () => showWorkflowListDialog(),
|
onClick: () => showWorkflowListDialog(),
|
||||||
});
|
});
|
||||||
const btnPublish = createButton(iconPublish, "发布当前工作流", {
|
const btnPublish = createButton(iconPublish, "发布当前工作流", {
|
||||||
onClick: () => showPublishDialog(),
|
onClick: () => showPublishDialog(),
|
||||||
});
|
});
|
||||||
|
const btnSettings = createButton(iconSettings, "设置工作流服务地址", {
|
||||||
const btnComfyServer = createButton(`🖥️`, "ComfyUI服务器管理", {
|
onClick: () => showSettingsDialog(),
|
||||||
onClick: () => showComfyServerDialog(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const separator = document.createElement("div");
|
const separator = document.createElement("div");
|
||||||
separator.style.cssText =
|
separator.style.cssText =
|
||||||
"border-left: 1px solid var(--border-color); height: 22px; margin: 0 4px;";
|
"border-left: 1px solid var(--border-color); height: 22px; margin: 0 4px;";
|
||||||
container.append(btnGet, btnPublish, btnComfyServer, separator);
|
container.append(btnGet, btnPublish, btnSettings, separator);
|
||||||
menu.insertBefore(container, menu.firstChild);
|
menu.insertBefore(container, menu.firstChild);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -581,10 +210,9 @@ app.registerExtension({
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const comfyServerDialog = new Modal(
|
const settingsDialog = new Modal(
|
||||||
"comfy-server-dialog",
|
"workflow-publisher-settings-dialog",
|
||||||
"ComfyUI服务器管理",
|
"设置工作流服务地址"
|
||||||
true
|
|
||||||
);
|
);
|
||||||
|
|
||||||
async function showWorkflowListDialog() {
|
async function showWorkflowListDialog() {
|
||||||
@@ -708,297 +336,93 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComfyUI 服务器管理对话框
|
// 设置对话框
|
||||||
async function showComfyServerDialog() {
|
async function showSettingsDialog() {
|
||||||
try {
|
try {
|
||||||
const serverConfig = getComfyServerConfig();
|
const currentHost = getWorkflowServiceHost();
|
||||||
const settings = getLocalSettings();
|
|
||||||
|
|
||||||
// 创建服务器配置表单
|
const settingsForm = `
|
||||||
const configForm = `
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 15px;">
|
<div style="display: flex; flex-direction: column; gap: 15px;">
|
||||||
<div>
|
<div>
|
||||||
<label for="server-name" style="display: block; margin-bottom: 5px;">当前服务节点名称:</label>
|
<label for="workflow-service-host" style="display: block; margin-bottom: 5px;">工作流服务地址:</label>
|
||||||
<input id="server-name" type="text" value="${
|
<input id="workflow-service-host" type="text" value="${currentHost}" placeholder="例如: http://localhost:8000" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 8px; border-radius: 4px;">
|
||||||
serverConfig.name
|
<small style="color: var(--border-color); font-size: 0.9em;">这是用于获取和发布工作流的服务地址</small>
|
||||||
}" placeholder="例如: comfyui_node_1" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 8px; border-radius: 4px;">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div id="connection-status" style="padding: 10px; border-radius: 4px; background: var(--comfy-input-bg); border: 1px solid var(--border-color);">
|
||||||
<label for="server-http-url" style="display: block; margin-bottom: 5px;">当前服务节点地址:</label>
|
<strong>连接状态:</strong> <span id="status-text">未测试</span>
|
||||||
<input id="server-http-url" type="text" value="${
|
|
||||||
serverConfig.http_url
|
|
||||||
}" placeholder="例如: http://127.0.0.1:8188" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 8px; border-radius: 4px;">
|
|
||||||
<small style="color: var(--border-color); font-size: 0.9em;">WebSocket地址将自动生成为: <span id="ws-url-preview">${
|
|
||||||
serverConfig.http_url
|
|
||||||
? serverConfig.http_url.replace(/^https?:\/\//, "ws://") +
|
|
||||||
"/ws"
|
|
||||||
: ""
|
|
||||||
}</span></small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="settings-actions" style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||||
|
<button id="btn-test-connection" class="jags-button" style="flex: 1; min-width: 120px;">测试连接</button>
|
||||||
<div>
|
<button id="btn-save-settings" class="jags-button" style="flex: 1; min-width: 120px;">保存设置</button>
|
||||||
<label for="workflow-service-host" style="display: block; margin-bottom: 5px;">工作流服务地址 (统一管理地址):</label>
|
|
||||||
<input id="workflow-service-host" type="text" value="${
|
|
||||||
settings.host
|
|
||||||
}" placeholder="例如: http://localhost:8000" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 8px; border-radius: 4px;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="server-status" style="padding: 10px; border-radius: 4px; background: var(--comfy-input-bg); border: 1px solid var(--border-color);">
|
|
||||||
<strong>服务器状态:</strong> <span id="status-text">未连接</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="server-actions" style="display: flex; gap: 10px; flex-wrap: wrap;">
|
|
||||||
<button id="btn-register" class="jags-button" style="flex: 1; min-width: 120px;">注册服务器</button>
|
|
||||||
<button id="btn-unregister" class="jags-button" style="flex: 1; min-width: 120px;">注销服务器</button>
|
|
||||||
<button id="btn-refresh-status" class="jags-button" style="flex: 1; min-width: 120px;">刷新状态</button>
|
|
||||||
<button id="btn-refresh-ip" class="jags-button" style="flex: 1; min-width: 120px;">刷新IP和端口</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
comfyServerDialog.setContent(configForm);
|
settingsDialog.setContent(settingsForm);
|
||||||
comfyServerDialog.addButtons([
|
settingsDialog.addButtons([
|
||||||
{ label: "关闭", callback: () => comfyServerDialog.hide() },
|
{ label: "关闭", callback: () => settingsDialog.hide() },
|
||||||
]);
|
]);
|
||||||
comfyServerDialog.show();
|
settingsDialog.show();
|
||||||
|
|
||||||
// 初始化 ComfyServerManager
|
|
||||||
const serverManager = new ComfyServerManager();
|
|
||||||
if (settings.host) {
|
|
||||||
serverManager.setWorkflowServiceHost(settings.host);
|
|
||||||
// 同步注册状态
|
|
||||||
serverManager.syncRegistrationStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定事件
|
// 绑定事件
|
||||||
const btnRegister = document.getElementById("btn-register");
|
const btnTestConnection = document.getElementById(
|
||||||
const btnUnregister = document.getElementById("btn-unregister");
|
"btn-test-connection"
|
||||||
const btnRefreshStatus = document.getElementById("btn-refresh-status");
|
);
|
||||||
const btnRefreshIP = document.getElementById("btn-refresh-ip");
|
const btnSaveSettings = document.getElementById("btn-save-settings");
|
||||||
const statusText = document.getElementById("status-text");
|
const statusText = document.getElementById("status-text");
|
||||||
|
const hostInput = document.getElementById("workflow-service-host");
|
||||||
|
|
||||||
// 添加HTTP地址输入框的实时预览功能
|
// 测试连接
|
||||||
const httpUrlInput = document.getElementById("server-http-url");
|
btnTestConnection.onclick = async () => {
|
||||||
const wsUrlPreview = document.getElementById("ws-url-preview");
|
btnTestConnection.disabled = true;
|
||||||
|
btnTestConnection.textContent = "测试中...";
|
||||||
httpUrlInput.addEventListener("input", function () {
|
statusText.textContent = "正在测试连接...";
|
||||||
const httpUrl = this.value;
|
statusText.style.color = "orange";
|
||||||
if (httpUrl) {
|
|
||||||
try {
|
|
||||||
const url = new URL(httpUrl);
|
|
||||||
// 根据协议自动选择ws或wss
|
|
||||||
const protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
||||||
const wsUrl = `${protocol}//${url.host}/ws`;
|
|
||||||
wsUrlPreview.textContent = wsUrl;
|
|
||||||
} catch (error) {
|
|
||||||
wsUrlPreview.textContent = "无效的URL格式";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wsUrlPreview.textContent = "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 注册服务器
|
|
||||||
btnRegister.onclick = async () => {
|
|
||||||
const newConfig = {
|
|
||||||
name: document.getElementById("server-name").value,
|
|
||||||
http_url: document.getElementById("server-http-url").value,
|
|
||||||
};
|
|
||||||
|
|
||||||
const workflowHost = document.getElementById(
|
|
||||||
"workflow-service-host"
|
|
||||||
).value;
|
|
||||||
if (workflowHost) {
|
|
||||||
serverManager.setWorkflowServiceHost(workflowHost);
|
|
||||||
// 保存到本地设置
|
|
||||||
saveLocalSettings({ host: workflowHost });
|
|
||||||
}
|
|
||||||
|
|
||||||
serverManager.updateConfig(newConfig);
|
|
||||||
|
|
||||||
btnRegister.disabled = true;
|
|
||||||
btnRegister.textContent = "注册中...";
|
|
||||||
|
|
||||||
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 {
|
|
||||||
const errorMessage = result.error || "未知错误";
|
|
||||||
statusText.textContent = `注册失败: ${errorMessage}`;
|
|
||||||
statusText.style.color = "var(--error-color)";
|
|
||||||
btnRegister.textContent = "注册服务器";
|
|
||||||
btnRegister.disabled = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 注销服务器
|
|
||||||
btnUnregister.onclick = async () => {
|
|
||||||
btnUnregister.disabled = true;
|
|
||||||
btnUnregister.textContent = "注销中...";
|
|
||||||
|
|
||||||
const result = await serverManager.unregisterServer();
|
|
||||||
if (result.success) {
|
|
||||||
statusText.textContent = "已注销";
|
|
||||||
statusText.style.color = "orange";
|
|
||||||
btnRegister.disabled = false;
|
|
||||||
btnRegister.textContent = "注册服务器";
|
|
||||||
btnUnregister.disabled = true;
|
|
||||||
btnUnregister.textContent = "注销服务器";
|
|
||||||
} else {
|
|
||||||
const errorMessage = result.error || "未知错误";
|
|
||||||
statusText.textContent = `注销失败: ${errorMessage}`;
|
|
||||||
statusText.style.color = "var(--error-color)";
|
|
||||||
btnUnregister.disabled = false;
|
|
||||||
btnUnregister.textContent = "注销服务器";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 刷新状态
|
|
||||||
btnRefreshStatus.onclick = async () => {
|
|
||||||
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) {
|
|
||||||
// 安全地处理时间显示
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 刷新IP地址和端口
|
|
||||||
btnRefreshIP.onclick = async () => {
|
|
||||||
btnRefreshIP.disabled = true;
|
|
||||||
btnRefreshIP.textContent = "刷新中...";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await serverManager.updateLocalIPAddress();
|
const testHost = hostInput.value.trim();
|
||||||
if (result) {
|
if (!testHost) {
|
||||||
// 更新输入框显示
|
throw new Error("请输入工作流服务地址");
|
||||||
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}`;
|
const response = await fetch(`${testHost}/api/workflow`);
|
||||||
|
if (response.ok) {
|
||||||
|
statusText.textContent = "连接成功";
|
||||||
statusText.style.color = "var(--success-color)";
|
statusText.style.color = "var(--success-color)";
|
||||||
} else {
|
} else {
|
||||||
statusText.textContent = "IP地址和端口无需更新";
|
throw new Error(
|
||||||
statusText.style.color = "orange";
|
`HTTP ${response.status}: ${response.statusText}`
|
||||||
}
|
|
||||||
} 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) {
|
} catch (error) {
|
||||||
console.warn("自动更新IP地址和端口失败:", error);
|
statusText.textContent = `连接失败: ${error.message}`;
|
||||||
|
statusText.style.color = "var(--error-color)";
|
||||||
|
} finally {
|
||||||
|
btnTestConnection.disabled = false;
|
||||||
|
btnTestConnection.textContent = "测试连接";
|
||||||
}
|
}
|
||||||
}, 500);
|
};
|
||||||
|
|
||||||
// 延迟同步按钮状态,确保与内部状态一致
|
// 保存设置
|
||||||
setTimeout(() => {
|
btnSaveSettings.onclick = () => {
|
||||||
if (serverManager.isRegistered) {
|
const newHost = hostInput.value.trim();
|
||||||
btnRegister.disabled = true;
|
if (!newHost) {
|
||||||
btnRegister.textContent = "已注册";
|
alert("请输入工作流服务地址");
|
||||||
btnUnregister.disabled = false;
|
return;
|
||||||
} else {
|
|
||||||
btnRegister.disabled = false;
|
|
||||||
btnRegister.textContent = "注册服务器";
|
|
||||||
btnUnregister.disabled = true;
|
|
||||||
}
|
}
|
||||||
}, 1000);
|
|
||||||
|
saveWorkflowServiceHost(newHost);
|
||||||
|
statusText.textContent = "设置已保存";
|
||||||
|
statusText.style.color = "var(--success-color)";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
settingsDialog.hide();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("显示ComfyUI服务器管理对话框失败:", error);
|
console.error("显示设置对话框失败:", error);
|
||||||
alert(`显示对话框失败: ${error.message}`);
|
alert(`显示对话框失败: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class ComfyUIServerInfo:
|
|||||||
http_url: str
|
http_url: str
|
||||||
ws_url: str
|
ws_url: str
|
||||||
status: ServerStatus = ServerStatus.OFFLINE
|
status: ServerStatus = ServerStatus.OFFLINE
|
||||||
last_heartbeat: Optional[datetime] = None
|
|
||||||
last_health_check: Optional[datetime] = None
|
last_health_check: Optional[datetime] = None
|
||||||
current_tasks: int = 0
|
current_tasks: int = 0
|
||||||
max_concurrent_tasks: int = 1
|
max_concurrent_tasks: int = 1
|
||||||
@@ -60,7 +59,6 @@ class ComfyUIServerInfo:
|
|||||||
http_url=model.http_url,
|
http_url=model.http_url,
|
||||||
ws_url=model.ws_url,
|
ws_url=model.ws_url,
|
||||||
status=ServerStatus(model.status),
|
status=ServerStatus(model.status),
|
||||||
last_heartbeat=model.last_heartbeat,
|
|
||||||
last_health_check=model.last_health_check,
|
last_health_check=model.last_health_check,
|
||||||
current_tasks=model.current_tasks,
|
current_tasks=model.current_tasks,
|
||||||
max_concurrent_tasks=model.max_concurrent_tasks,
|
max_concurrent_tasks=model.max_concurrent_tasks,
|
||||||
@@ -75,7 +73,6 @@ class ComfyUIServerInfo:
|
|||||||
http_url=self.http_url,
|
http_url=self.http_url,
|
||||||
ws_url=self.ws_url,
|
ws_url=self.ws_url,
|
||||||
status=self.status.value,
|
status=self.status.value,
|
||||||
last_heartbeat=self.last_heartbeat,
|
|
||||||
last_health_check=self.last_health_check,
|
last_health_check=self.last_health_check,
|
||||||
current_tasks=self.current_tasks,
|
current_tasks=self.current_tasks,
|
||||||
max_concurrent_tasks=self.max_concurrent_tasks,
|
max_concurrent_tasks=self.max_concurrent_tasks,
|
||||||
@@ -91,7 +88,6 @@ class ComfyUIServerManager:
|
|||||||
self.servers: Dict[str, ComfyUIServerInfo] = {} # name -> server_info
|
self.servers: Dict[str, ComfyUIServerInfo] = {} # name -> server_info
|
||||||
self.lock = asyncio.Lock()
|
self.lock = asyncio.Lock()
|
||||||
self.health_check_interval = 30 # 健康检查间隔(秒)
|
self.health_check_interval = 30 # 健康检查间隔(秒)
|
||||||
self.heartbeat_timeout = 60 # 心跳超时时间(秒)
|
|
||||||
self._health_check_task: Optional[asyncio.Task] = None
|
self._health_check_task: Optional[asyncio.Task] = None
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
|
|
||||||
@@ -145,7 +141,6 @@ class ComfyUIServerManager:
|
|||||||
http_url=server.http_url,
|
http_url=server.http_url,
|
||||||
ws_url=server.ws_url,
|
ws_url=server.ws_url,
|
||||||
status=server.status.value,
|
status=server.status.value,
|
||||||
last_heartbeat=server.last_heartbeat,
|
|
||||||
last_health_check=server.last_health_check,
|
last_health_check=server.last_health_check,
|
||||||
current_tasks=server.current_tasks,
|
current_tasks=server.current_tasks,
|
||||||
max_concurrent_tasks=server.max_concurrent_tasks,
|
max_concurrent_tasks=server.max_concurrent_tasks,
|
||||||
@@ -238,7 +233,6 @@ class ComfyUIServerManager:
|
|||||||
if await self._check_server_health(server_info):
|
if await self._check_server_health(server_info):
|
||||||
server_info.status = ServerStatus.ONLINE
|
server_info.status = ServerStatus.ONLINE
|
||||||
server_info.last_health_check = datetime.now()
|
server_info.last_health_check = datetime.now()
|
||||||
server_info.last_heartbeat = datetime.now()
|
|
||||||
|
|
||||||
self.servers[name] = server_info
|
self.servers[name] = server_info
|
||||||
|
|
||||||
@@ -263,17 +257,6 @@ class ComfyUIServerManager:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def update_server_heartbeat(self, name: str) -> bool:
|
|
||||||
"""更新服务器心跳"""
|
|
||||||
async with self.lock:
|
|
||||||
if name in self.servers:
|
|
||||||
self.servers[name].last_heartbeat = datetime.now()
|
|
||||||
self.servers[name].status = ServerStatus.ONLINE
|
|
||||||
# 保存到数据库
|
|
||||||
await self._save_server_to_db(self.servers[name])
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def get_available_server(
|
async def get_available_server(
|
||||||
self, required_capabilities: Optional[Dict[str, any]] = None
|
self, required_capabilities: Optional[Dict[str, any]] = None
|
||||||
) -> Optional[ComfyUIServerInfo]:
|
) -> Optional[ComfyUIServerInfo]:
|
||||||
@@ -401,17 +384,6 @@ class ComfyUIServerManager:
|
|||||||
|
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
for server in self.servers.values():
|
for server in self.servers.values():
|
||||||
# 检查心跳超时
|
|
||||||
if (
|
|
||||||
server.last_heartbeat
|
|
||||||
and current_time - server.last_heartbeat
|
|
||||||
> timedelta(seconds=self.heartbeat_timeout)
|
|
||||||
):
|
|
||||||
server.status = ServerStatus.OFFLINE
|
|
||||||
logger.warning(f"服务器 {server.name} 心跳超时,标记为离线")
|
|
||||||
# 保存到数据库
|
|
||||||
await self._save_server_to_db(server)
|
|
||||||
|
|
||||||
# 定期健康检查
|
# 定期健康检查
|
||||||
if (
|
if (
|
||||||
not server.last_health_check
|
not server.last_health_check
|
||||||
@@ -424,8 +396,7 @@ class ComfyUIServerManager:
|
|||||||
|
|
||||||
if is_healthy and server.status != ServerStatus.ONLINE:
|
if is_healthy and server.status != ServerStatus.ONLINE:
|
||||||
server.status = ServerStatus.ONLINE
|
server.status = ServerStatus.ONLINE
|
||||||
server.last_heartbeat = current_time # 同步更新心跳时间
|
logger.info(f"服务器 {server.name} 恢复在线")
|
||||||
logger.info(f"服务器 {server.name} 恢复在线,心跳时间已同步")
|
|
||||||
elif not is_healthy and server.status == ServerStatus.ONLINE:
|
elif not is_healthy and server.status == ServerStatus.ONLINE:
|
||||||
server.status = ServerStatus.ERROR
|
server.status = ServerStatus.ERROR
|
||||||
logger.warning(f"服务器 {server.name} 健康检查失败")
|
logger.warning(f"服务器 {server.name} 健康检查失败")
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ class ComfyUIServer(Base):
|
|||||||
http_url = Column(String, nullable=False)
|
http_url = Column(String, nullable=False)
|
||||||
ws_url = Column(String, nullable=False)
|
ws_url = Column(String, nullable=False)
|
||||||
status = Column(String, nullable=False, default="offline")
|
status = Column(String, nullable=False, default="offline")
|
||||||
last_heartbeat = Column(DateTime)
|
|
||||||
last_health_check = Column(DateTime)
|
last_health_check = Column(DateTime)
|
||||||
current_tasks = Column(Integer, default=0)
|
current_tasks = Column(Integer, default=0)
|
||||||
max_concurrent_tasks = Column(Integer, default=1)
|
max_concurrent_tasks = Column(Integer, default=1)
|
||||||
@@ -135,7 +134,6 @@ class ComfyUIServer(Base):
|
|||||||
"http_url": self.http_url,
|
"http_url": self.http_url,
|
||||||
"ws_url": self.ws_url,
|
"ws_url": self.ws_url,
|
||||||
"status": self.status,
|
"status": self.status,
|
||||||
"last_heartbeat": self.last_heartbeat.isoformat() if self.last_heartbeat else None,
|
|
||||||
"last_health_check": self.last_health_check.isoformat() if self.last_health_check else None,
|
"last_health_check": self.last_health_check.isoformat() if self.last_health_check else None,
|
||||||
"current_tasks": self.current_tasks,
|
"current_tasks": self.current_tasks,
|
||||||
"max_concurrent_tasks": self.max_concurrent_tasks,
|
"max_concurrent_tasks": self.max_concurrent_tasks,
|
||||||
|
|||||||
@@ -22,12 +22,6 @@ class ServerRegistrationRequest(BaseModel):
|
|||||||
ws_url: str
|
ws_url: str
|
||||||
|
|
||||||
|
|
||||||
class ServerHeartbeatRequest(BaseModel):
|
|
||||||
"""服务器心跳请求模型"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
|
||||||
class ServerUnregisterRequest(BaseModel):
|
class ServerUnregisterRequest(BaseModel):
|
||||||
"""服务器注销请求模型"""
|
"""服务器注销请求模型"""
|
||||||
|
|
||||||
@@ -41,7 +35,6 @@ class ServerStatusResponse(BaseModel):
|
|||||||
http_url: str
|
http_url: str
|
||||||
ws_url: str
|
ws_url: str
|
||||||
status: str
|
status: str
|
||||||
last_heartbeat: Optional[str] = None
|
|
||||||
last_health_check: Optional[str] = None
|
last_health_check: Optional[str] = None
|
||||||
current_tasks: int
|
current_tasks: int
|
||||||
max_concurrent_tasks: int
|
max_concurrent_tasks: int
|
||||||
@@ -74,25 +67,6 @@ async def register_server(request: ServerRegistrationRequest):
|
|||||||
raise HTTPException(status_code=500, detail=f"注册失败: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"注册失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/heartbeat", response_model=Dict[str, str])
|
|
||||||
async def update_heartbeat(request: ServerHeartbeatRequest):
|
|
||||||
"""更新服务器心跳"""
|
|
||||||
try:
|
|
||||||
success = await server_manager.update_server_heartbeat(request.name)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
return {
|
|
||||||
"message": f"服务器 {request.name} 心跳更新成功",
|
|
||||||
"status": "success",
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise HTTPException(status_code=404, detail=f"服务器 {request.name} 不存在")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"更新服务器 {request.name} 心跳时发生错误: {e}")
|
|
||||||
raise HTTPException(status_code=500, detail=f"心跳更新失败: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/unregister", response_model=Dict[str, str])
|
@router.post("/unregister", response_model=Dict[str, str])
|
||||||
async def unregister_server(request: ServerUnregisterRequest):
|
async def unregister_server(request: ServerUnregisterRequest):
|
||||||
"""注销ComfyUI服务器"""
|
"""注销ComfyUI服务器"""
|
||||||
@@ -124,11 +98,6 @@ async def get_server_status(server_name: str):
|
|||||||
http_url=server_info.http_url,
|
http_url=server_info.http_url,
|
||||||
ws_url=server_info.ws_url,
|
ws_url=server_info.ws_url,
|
||||||
status=server_info.status.value,
|
status=server_info.status.value,
|
||||||
last_heartbeat=(
|
|
||||||
server_info.last_heartbeat.isoformat()
|
|
||||||
if server_info.last_heartbeat
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
last_health_check=(
|
last_health_check=(
|
||||||
server_info.last_health_check.isoformat()
|
server_info.last_health_check.isoformat()
|
||||||
if server_info.last_health_check
|
if server_info.last_health_check
|
||||||
@@ -157,9 +126,6 @@ async def list_all_servers():
|
|||||||
http_url=server.http_url,
|
http_url=server.http_url,
|
||||||
ws_url=server.ws_url,
|
ws_url=server.ws_url,
|
||||||
status=server.status.value,
|
status=server.status.value,
|
||||||
last_heartbeat=(
|
|
||||||
server.last_heartbeat.isoformat() if server.last_heartbeat else None
|
|
||||||
),
|
|
||||||
last_health_check=(
|
last_health_check=(
|
||||||
server.last_health_check.isoformat()
|
server.last_health_check.isoformat()
|
||||||
if server.last_health_check
|
if server.last_health_check
|
||||||
|
|||||||
@@ -430,16 +430,16 @@ async def monitor_dashboard():
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 格式化心跳时间,突出显示状态
|
// 格式化健康检查时间
|
||||||
const formatHeartbeat = (timeStr) => {
|
const formatHealthCheck = (timeStr) => {
|
||||||
if (!timeStr) return 'N/A';
|
if (!timeStr) return 'N/A';
|
||||||
try {
|
try {
|
||||||
const date = new Date(timeStr);
|
const date = new Date(timeStr);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const diff = now - date;
|
const diff = now - date;
|
||||||
|
|
||||||
if (diff > 86400000) return '⚠️ 心跳超时'; // 超过1天
|
if (diff > 86400000) return '⚠️ 检查超时'; // 超过1天
|
||||||
if (diff > 3600000) return '⚠️ 心跳延迟'; // 超过1小时
|
if (diff > 3600000) return '⚠️ 检查延迟'; // 超过1小时
|
||||||
if (diff < 60000) return '✅ 刚刚';
|
if (diff < 60000) return '✅ 刚刚';
|
||||||
if (diff < 3600000) return `✅ ${Math.floor(diff / 60000)}分钟前`;
|
if (diff < 3600000) return `✅ ${Math.floor(diff / 60000)}分钟前`;
|
||||||
return `✅ ${Math.floor(diff / 3600000)}小时前`;
|
return `✅ ${Math.floor(diff / 3600000)}小时前`;
|
||||||
@@ -465,11 +465,7 @@ async def monitor_dashboard():
|
|||||||
</div>
|
</div>
|
||||||
<span class="info-separator">|</span>
|
<span class="info-separator">|</span>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="status-text">心跳: ${formatHeartbeat(server.last_heartbeat)}</span>
|
<span class="status-text">检查: ${formatHealthCheck(server.last_health_check)}</span>
|
||||||
</div>
|
|
||||||
<span class="info-separator">|</span>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="status-text">检查: ${formatTime(server.last_health_check)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${server.error ? `<div class="error-message">${server.error}</div>` : ''}
|
${server.error ? `<div class="error-message">${server.error}</div>` : ''}
|
||||||
@@ -607,9 +603,6 @@ async def get_server_status() -> List[Dict[str, Any]]:
|
|||||||
"max_concurrent_tasks": getattr(
|
"max_concurrent_tasks": getattr(
|
||||||
server, "max_concurrent_tasks", 1
|
server, "max_concurrent_tasks", 1
|
||||||
),
|
),
|
||||||
"last_heartbeat": getattr(
|
|
||||||
server, "last_heartbeat", None
|
|
||||||
),
|
|
||||||
"last_health_check": getattr(
|
"last_health_check": getattr(
|
||||||
server, "last_health_check", None
|
server, "last_health_check", None
|
||||||
),
|
),
|
||||||
@@ -625,9 +618,6 @@ async def get_server_status() -> List[Dict[str, Any]]:
|
|||||||
"max_concurrent_tasks": getattr(
|
"max_concurrent_tasks": getattr(
|
||||||
server, "max_concurrent_tasks", 1
|
server, "max_concurrent_tasks", 1
|
||||||
),
|
),
|
||||||
"last_heartbeat": getattr(
|
|
||||||
server, "last_heartbeat", None
|
|
||||||
),
|
|
||||||
"last_health_check": getattr(
|
"last_health_check": getattr(
|
||||||
server, "last_health_check", None
|
server, "last_health_check", None
|
||||||
),
|
),
|
||||||
@@ -641,7 +631,6 @@ async def get_server_status() -> List[Dict[str, Any]]:
|
|||||||
"error": str(e),
|
"error": str(e),
|
||||||
"current_tasks": getattr(server, "current_tasks", 0),
|
"current_tasks": getattr(server, "current_tasks", 0),
|
||||||
"max_concurrent_tasks": getattr(server, "max_concurrent_tasks", 1),
|
"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),
|
"last_health_check": getattr(server, "last_health_check", None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user