feat: 添加ComfyUI服务器管理功能,重构相关代码以支持动态注册和状态监控,增强系统的可扩展性和可维护性
This commit is contained in:
2
.env
2
.env
@@ -22,7 +22,7 @@
|
||||
# "input_dir": "waas/comfyui_4/input",
|
||||
# "output_dir": "waas/comfyui_4/output"
|
||||
# }
|
||||
COMFYUI_SERVERS_JSON='[{"http_url":"http://127.0.0.1:8000","ws_url":"ws://127.0.0.1:8000/ws","input_dir":"waas/comfyui_4/input","output_dir":"waas/comfyui_4/output"}]'
|
||||
# COMFYUI_SERVERS_JSON='[{"http_url":"http://127.0.0.1:8000","ws_url":"ws://127.0.0.1:8000/ws","input_dir":"waas/comfyui_4/input","output_dir":"waas/comfyui_4/output"}]'
|
||||
|
||||
DB_FILE="workflows_service.sqlite"
|
||||
|
||||
|
||||
524
js/publisher.js
524
js/publisher.js
@@ -2,12 +2,39 @@ import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
// 本地存储的 key
|
||||
const SETTINGS_STORAGE_KEY = "comfyui_publisher_settings";
|
||||
const COMFY_SERVER_REGISTRATION_KEY = "comfyui_server_registration";
|
||||
|
||||
// ComfyUI服务器注册配置
|
||||
const DEFAULT_SERVER_CONFIG = {
|
||||
name: "comfyui_node_1",
|
||||
http_url: "http://127.0.0.1:8188",
|
||||
workflow_service_host: "http://localhost:8000",
|
||||
};
|
||||
|
||||
// 获取ComfyUI服务器注册配置
|
||||
function getComfyServerConfig() {
|
||||
try {
|
||||
const stored = localStorage.getItem(COMFY_SERVER_REGISTRATION_KEY);
|
||||
return stored ? JSON.parse(stored) : { ...DEFAULT_SERVER_CONFIG };
|
||||
} catch (error) {
|
||||
console.error("读取ComfyUI服务器配置失败:", error);
|
||||
return { ...DEFAULT_SERVER_CONFIG };
|
||||
}
|
||||
}
|
||||
|
||||
// 保存ComfyUI服务器注册配置
|
||||
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(SETTINGS_STORAGE_KEY);
|
||||
const stored = localStorage.getItem("workflow_publisher_settings");
|
||||
return stored ? JSON.parse(stored) : { host: "" };
|
||||
} catch (error) {
|
||||
console.error("读取本地设置失败:", error);
|
||||
@@ -15,32 +42,247 @@ function getLocalSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置到本地
|
||||
// 保存本地设置
|
||||
function saveLocalSettings(settings) {
|
||||
try {
|
||||
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
||||
localStorage.setItem(
|
||||
"workflow_publisher_settings",
|
||||
JSON.stringify(settings)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("保存本地设置失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义 fetchApi 函数,使用本地存储的 host
|
||||
// ComfyUI服务器注册管理器
|
||||
class ComfyServerManager {
|
||||
constructor() {
|
||||
this.registrationInterval = null;
|
||||
this.heartbeatInterval = null;
|
||||
this.isRegistered = false;
|
||||
this.serverConfig = getComfyServerConfig();
|
||||
}
|
||||
|
||||
// 注册服务器到工作流服务
|
||||
async registerServer() {
|
||||
if (!this.serverConfig.workflow_service_host) {
|
||||
console.warn("未设置工作流服务地址,无法注册");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 true;
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
console.error("ComfyUI服务器注册失败:", response.status, errorText);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("注册ComfyUI服务器时发生错误:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 注销服务器
|
||||
async unregisterServer() {
|
||||
if (!this.serverConfig.workflow_service_host || !this.isRegistered) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 true;
|
||||
} else {
|
||||
console.error("ComfyUI服务器注销失败:", response.status);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("注销ComfyUI服务器时发生错误:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 发送心跳
|
||||
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(() => {
|
||||
setTimeout(() => this.registerServer(), 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 设置工作流服务地址
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义 fetchApi 函数,使用ComfyUI服务器配置中的工作流服务地址
|
||||
async function customFetchApi(endpoint, options = {}) {
|
||||
try {
|
||||
// 从本地存储获取设置
|
||||
const settings = getLocalSettings();
|
||||
// 从ComfyUI服务器配置获取工作流服务地址
|
||||
const serverConfig = getComfyServerConfig();
|
||||
|
||||
// 如果没有配置 host,回退到默认的 api.fetchApi
|
||||
if (!settings.host) {
|
||||
console.warn("未配置 host,使用默认的 ComfyUI API");
|
||||
// 如果没有配置工作流服务地址,回退到默认的 api.fetchApi
|
||||
if (!serverConfig.workflow_service_host) {
|
||||
console.warn("未配置工作流服务地址,使用默认的 ComfyUI API");
|
||||
return await api.fetchApi(endpoint, options);
|
||||
}
|
||||
|
||||
// 构建完整的 URL
|
||||
const host = settings.host.replace(/\/$/, ""); // 移除末尾的斜杠
|
||||
const host = serverConfig.workflow_service_host.replace(/\/$/, ""); // 移除末尾的斜杠
|
||||
const fullUrl = `${host}${endpoint}`;
|
||||
|
||||
console.log(`使用本地存储的 host 请求: ${fullUrl}`);
|
||||
console.log(`使用工作流服务地址请求: ${fullUrl}`);
|
||||
|
||||
// 使用 fetch 发送请求
|
||||
const response = await fetch(fullUrl, {
|
||||
@@ -186,22 +428,18 @@ app.registerExtension({
|
||||
const btnPublish = createButton(iconPublish, "发布当前工作流", {
|
||||
onClick: () => showPublishDialog(),
|
||||
});
|
||||
const btnSettings = createButton(`⚙️`, "发布设置", {
|
||||
onClick: () => showSettingsDialog(),
|
||||
|
||||
const btnComfyServer = createButton(`🖥️`, "ComfyUI服务器管理", {
|
||||
onClick: () => showComfyServerDialog(),
|
||||
});
|
||||
|
||||
const separator = document.createElement("div");
|
||||
separator.style.cssText =
|
||||
"border-left: 1px solid var(--border-color); height: 22px; margin: 0 4px;";
|
||||
container.append(btnGet, btnPublish, btnSettings, separator);
|
||||
container.append(btnGet, btnPublish, btnComfyServer, separator);
|
||||
menu.insertBefore(container, menu.firstChild);
|
||||
});
|
||||
|
||||
const settingsDialog = new Modal(
|
||||
"workflow-publisher-settings-dialog",
|
||||
"发布设置"
|
||||
);
|
||||
|
||||
const publishDialog = new Modal(
|
||||
"workflow-publisher-publish-dialog",
|
||||
"发布工作流"
|
||||
@@ -213,6 +451,12 @@ app.registerExtension({
|
||||
true
|
||||
);
|
||||
|
||||
const comfyServerDialog = new Modal(
|
||||
"comfy-server-dialog",
|
||||
"ComfyUI服务器管理",
|
||||
true
|
||||
);
|
||||
|
||||
async function showWorkflowListDialog() {
|
||||
workflowListDialog.setContent(
|
||||
'<p style="text-align:center;">正在从服务器加载工作流...</p>'
|
||||
@@ -318,76 +562,8 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
async function showSettingsDialog() {
|
||||
try {
|
||||
// 首先尝试从本地存储获取设置
|
||||
const settings = getLocalSettings();
|
||||
|
||||
settingsDialog.setContent(`
|
||||
<label for="publisher-host" style="display: block; margin-bottom: 5px;">Host:</label>
|
||||
<input id="publisher-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: 5px; border-radius: 4px; margin-bottom: 10px;">
|
||||
`);
|
||||
settingsDialog.addButtons([
|
||||
{ label: "取消", callback: () => settingsDialog.hide() },
|
||||
{ label: "保存", callback: saveSettings },
|
||||
]);
|
||||
settingsDialog.show();
|
||||
} catch (error) {
|
||||
console.error("加载设置失败:", error);
|
||||
alert(`加载设置失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
const host = document.getElementById("publisher-host").value;
|
||||
|
||||
// 构建新的设置对象
|
||||
const newSettings = { host: host };
|
||||
|
||||
try {
|
||||
// 保存到本地存储
|
||||
saveLocalSettings(newSettings);
|
||||
|
||||
alert("设置已保存到本地!");
|
||||
settingsDialog.hide();
|
||||
} catch (error) {
|
||||
alert(`保存设置失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function showPublishDialog() {
|
||||
try {
|
||||
// 从本地存储获取设置
|
||||
let settings = getLocalSettings();
|
||||
|
||||
// 如果本地没有设置,尝试从服务器获取
|
||||
if (!settings.host) {
|
||||
try {
|
||||
const settingsResp = await customFetchApi("/publisher/settings", {
|
||||
cache: "no-store",
|
||||
});
|
||||
if (settingsResp.ok) {
|
||||
const text = await settingsResp.text();
|
||||
try {
|
||||
const serverSettings = text ? JSON.parse(text) : {};
|
||||
settings = { ...settings, ...serverSettings };
|
||||
// 保存到本地存储
|
||||
saveLocalSettings(settings);
|
||||
} catch (parseError) {
|
||||
console.error("JSON解析失败:", parseError, "原始响应:", text);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("从服务器获取设置失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 确保设置对象有必需的字段
|
||||
settings = {
|
||||
host: settings.host || "",
|
||||
...settings,
|
||||
};
|
||||
|
||||
publishDialog.setContent(
|
||||
`<label for="publisher-workflow-name" style="display: block; margin-bottom: 5px;">工作流基础名称:</label><input id="publisher-workflow-name" type="text" placeholder="例如: SDXL高级出图" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 5px; border-radius: 4px;"><p id="publisher-status" style="margin-top: 10px; color: var(--success-color); min-height: 1.2em;"></p>`
|
||||
);
|
||||
@@ -397,8 +573,176 @@ app.registerExtension({
|
||||
]);
|
||||
publishDialog.show();
|
||||
} catch (error) {
|
||||
console.error("加载设置失败:", error);
|
||||
alert(`加载设置失败: ${error.message}`);
|
||||
console.error("显示发布对话框失败:", error);
|
||||
alert(`显示对话框失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ComfyUI 服务器管理对话框
|
||||
async function showComfyServerDialog() {
|
||||
try {
|
||||
const serverConfig = getComfyServerConfig();
|
||||
const settings = getLocalSettings();
|
||||
|
||||
// 创建服务器配置表单
|
||||
const configForm = `
|
||||
<div style="display: flex; flex-direction: column; gap: 15px;">
|
||||
<div>
|
||||
<label for="server-name" style="display: block; margin-bottom: 5px;">当前服务节点名称:</label>
|
||||
<input id="server-name" type="text" value="${serverConfig.name}" 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>
|
||||
<label for="server-http-url" style="display: block; margin-bottom: 5px;">当前服务节点地址:</label>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
comfyServerDialog.setContent(configForm);
|
||||
comfyServerDialog.addButtons([
|
||||
{ label: "关闭", callback: () => comfyServerDialog.hide() },
|
||||
]);
|
||||
comfyServerDialog.show();
|
||||
|
||||
// 初始化 ComfyServerManager
|
||||
const serverManager = new ComfyServerManager();
|
||||
if (settings.host) {
|
||||
serverManager.setWorkflowServiceHost(settings.host);
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
const btnRegister = document.getElementById("btn-register");
|
||||
const btnUnregister = document.getElementById("btn-unregister");
|
||||
const btnRefreshStatus = document.getElementById("btn-refresh-status");
|
||||
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() {
|
||||
const httpUrl = this.value;
|
||||
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 success = await serverManager.registerServer();
|
||||
if (success) {
|
||||
statusText.textContent = "已注册";
|
||||
statusText.style.color = "var(--success-color)";
|
||||
btnRegister.textContent = "已注册";
|
||||
btnRegister.disabled = true;
|
||||
btnUnregister.disabled = false;
|
||||
} else {
|
||||
statusText.textContent = "注册失败";
|
||||
statusText.style.color = "var(--error-color)";
|
||||
btnRegister.textContent = "注册服务器";
|
||||
btnRegister.disabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 注销服务器
|
||||
btnUnregister.onclick = async () => {
|
||||
btnUnregister.disabled = true;
|
||||
btnUnregister.textContent = "注销中...";
|
||||
|
||||
const success = await serverManager.unregisterServer();
|
||||
if (success) {
|
||||
statusText.textContent = "已注销";
|
||||
statusText.style.color = "orange";
|
||||
btnRegister.disabled = false;
|
||||
btnRegister.textContent = "注册服务器";
|
||||
btnUnregister.disabled = true;
|
||||
btnUnregister.textContent = "注销服务器";
|
||||
} else {
|
||||
statusText.textContent = "注销失败";
|
||||
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();
|
||||
if (status) {
|
||||
statusText.textContent = `在线 - 最后心跳: ${new Date(
|
||||
status.last_heartbeat
|
||||
).toLocaleString()}`;
|
||||
statusText.style.color = "var(--success-color)";
|
||||
btnRegister.disabled = true;
|
||||
btnRegister.textContent = "已注册";
|
||||
btnUnregister.disabled = false;
|
||||
} else {
|
||||
statusText.textContent = "未注册";
|
||||
statusText.style.color = "orange";
|
||||
btnRegister.disabled = false;
|
||||
btnRegister.textContent = "注册服务器";
|
||||
btnUnregister.disabled = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 初始状态检查
|
||||
if (settings.host) {
|
||||
btnRefreshStatus.click();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("显示ComfyUI服务器管理对话框失败:", error);
|
||||
alert(`显示对话框失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,7 +12,8 @@ import aiohttp
|
||||
from aiohttp import ClientTimeout
|
||||
|
||||
from workflow_service.comfy.comfy_workflow import build_prompt
|
||||
from workflow_service.config import Settings, ComfyUIServer
|
||||
from workflow_service.config import Settings
|
||||
from workflow_service.comfy.comfy_server import server_manager, ComfyUIServerInfo
|
||||
from workflow_service.database.api import (
|
||||
create_workflow_run,
|
||||
update_workflow_run_status,
|
||||
@@ -105,6 +106,9 @@ class WorkflowQueueManager:
|
||||
workflow_run_id = self.pending_tasks.pop(0)
|
||||
server = available_servers[0]
|
||||
|
||||
# 分配服务器资源
|
||||
await server_manager.allocate_server(server.name)
|
||||
|
||||
# 标记任务为运行中
|
||||
await update_workflow_run_status(
|
||||
workflow_run_id, "running", server.http_url
|
||||
@@ -112,28 +116,21 @@ class WorkflowQueueManager:
|
||||
self.running_tasks[server.http_url] = {
|
||||
"workflow_run_id": workflow_run_id,
|
||||
"started_at": datetime.now(),
|
||||
"server_name": server.name,
|
||||
}
|
||||
|
||||
# 启动任务执行
|
||||
asyncio.create_task(self._execute_task(workflow_run_id, server))
|
||||
|
||||
async def _get_available_servers(self) -> list[ComfyUIServer]:
|
||||
async def _get_available_servers(self) -> list[ComfyUIServerInfo]:
|
||||
"""获取可用的服务器"""
|
||||
available_servers = []
|
||||
for server in settings.SERVERS:
|
||||
if server.http_url not in self.running_tasks:
|
||||
# 检查服务器状态
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
status = await self.get_server_status(server, session)
|
||||
if status["is_reachable"] and status["is_free"]:
|
||||
available_servers.append(server)
|
||||
except Exception as e:
|
||||
logger.warning(f"检查服务器 {server.http_url} 状态时出错: {e}")
|
||||
# 使用新的服务器管理器获取可用服务器
|
||||
available_server = await server_manager.get_available_server()
|
||||
if available_server:
|
||||
return [available_server]
|
||||
return []
|
||||
|
||||
return available_servers
|
||||
|
||||
async def _execute_task(self, workflow_run_id: str, server: ComfyUIServer):
|
||||
async def _execute_task(self, workflow_run_id: str, server: ComfyUIServerInfo):
|
||||
"""执行任务"""
|
||||
cleanup_paths = []
|
||||
try:
|
||||
@@ -179,6 +176,9 @@ class WorkflowQueueManager:
|
||||
async with self.lock:
|
||||
if server.http_url in self.running_tasks:
|
||||
del self.running_tasks[server.http_url]
|
||||
|
||||
# 释放服务器资源
|
||||
await server_manager.release_server(server.name)
|
||||
|
||||
# 继续处理队列
|
||||
asyncio.create_task(self._process_queue())
|
||||
@@ -211,7 +211,7 @@ class WorkflowQueueManager:
|
||||
return result
|
||||
|
||||
async def get_server_status(
|
||||
self, server: ComfyUIServer, session: aiohttp.ClientSession
|
||||
self, server: ComfyUIServerInfo, session: aiohttp.ClientSession
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
检查单个ComfyUI服务器的详细状态。
|
||||
@@ -256,7 +256,7 @@ async def _execute_prompt_on_server(
|
||||
workflow_data: dict,
|
||||
api_spec: dict,
|
||||
request_data: dict,
|
||||
server: ComfyUIServer,
|
||||
server: ComfyUIServerInfo,
|
||||
workflow_run_id: str,
|
||||
) -> dict:
|
||||
"""
|
||||
|
||||
271
workflow_service/comfy/comfy_server.py
Normal file
271
workflow_service/comfy/comfy_server.py
Normal file
@@ -0,0 +1,271 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Set
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import ClientTimeout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServerStatus(Enum):
|
||||
"""服务器状态枚举"""
|
||||
ONLINE = "online"
|
||||
OFFLINE = "offline"
|
||||
BUSY = "busy"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIServerInfo:
|
||||
"""ComfyUI服务器信息"""
|
||||
name: str # 服务器名称,由注册方自行拟定
|
||||
http_url: str
|
||||
ws_url: str
|
||||
status: ServerStatus = ServerStatus.OFFLINE
|
||||
last_heartbeat: Optional[datetime] = None
|
||||
last_health_check: Optional[datetime] = None
|
||||
current_tasks: int = 0
|
||||
max_concurrent_tasks: int = 1
|
||||
capabilities: Dict[str, any] = None # 服务器能力信息,如支持的模型等
|
||||
metadata: Dict[str, any] = None # 其他元数据
|
||||
|
||||
def __post_init__(self):
|
||||
if self.capabilities is None:
|
||||
self.capabilities = {}
|
||||
if self.metadata is None:
|
||||
self.metadata = {}
|
||||
|
||||
|
||||
class ComfyUIServerManager:
|
||||
"""ComfyUI服务器管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.servers: Dict[str, ComfyUIServerInfo] = {} # name -> server_info
|
||||
self.lock = asyncio.Lock()
|
||||
self.health_check_interval = 30 # 健康检查间隔(秒)
|
||||
self.heartbeat_timeout = 60 # 心跳超时时间(秒)
|
||||
self._health_check_task: Optional[asyncio.Task] = None
|
||||
# 延迟启动健康检查,避免在模块导入时启动异步任务
|
||||
# self._start_health_check()
|
||||
|
||||
async def register_server(
|
||||
self,
|
||||
name: str,
|
||||
http_url: str,
|
||||
ws_url: str,
|
||||
max_concurrent_tasks: int = 1,
|
||||
capabilities: Optional[Dict[str, any]] = None,
|
||||
metadata: Optional[Dict[str, any]] = None
|
||||
) -> bool:
|
||||
"""注册新的ComfyUI服务器"""
|
||||
# 确保健康检查任务已启动
|
||||
await self._ensure_health_check_started()
|
||||
|
||||
async with self.lock:
|
||||
if name in self.servers:
|
||||
logger.warning(f"服务器名称 {name} 已存在,将更新配置")
|
||||
|
||||
server_info = ComfyUIServerInfo(
|
||||
name=name,
|
||||
http_url=http_url.rstrip('/'),
|
||||
ws_url=ws_url.rstrip('/'),
|
||||
status=ServerStatus.OFFLINE, # 注册时状态为离线,等待健康检查
|
||||
max_concurrent_tasks=max_concurrent_tasks,
|
||||
capabilities=capabilities or {},
|
||||
metadata=metadata or {}
|
||||
)
|
||||
|
||||
# 立即进行健康检查
|
||||
if await self._check_server_health(server_info):
|
||||
server_info.status = ServerStatus.ONLINE
|
||||
server_info.last_health_check = datetime.now()
|
||||
server_info.last_heartbeat = datetime.now()
|
||||
|
||||
self.servers[name] = server_info
|
||||
logger.info(f"服务器 {name} 注册成功: {http_url}")
|
||||
return True
|
||||
|
||||
async def unregister_server(self, name: str) -> bool:
|
||||
"""注销ComfyUI服务器"""
|
||||
async with self.lock:
|
||||
if name in self.servers:
|
||||
del self.servers[name]
|
||||
logger.info(f"服务器 {name} 已注销")
|
||||
return True
|
||||
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
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_available_server(self, required_capabilities: Optional[Dict[str, any]] = None) -> Optional[ComfyUIServerInfo]:
|
||||
"""获取可用的服务器(负载均衡)"""
|
||||
# 确保健康检查任务已启动
|
||||
await self._ensure_health_check_started()
|
||||
|
||||
async with self.lock:
|
||||
available_servers = []
|
||||
|
||||
for server in self.servers.values():
|
||||
if (server.status == ServerStatus.ONLINE and
|
||||
server.current_tasks < server.max_concurrent_tasks):
|
||||
|
||||
# 检查能力要求
|
||||
if required_capabilities:
|
||||
if not self._check_capabilities(server.capabilities, required_capabilities):
|
||||
continue
|
||||
|
||||
available_servers.append(server)
|
||||
|
||||
if not available_servers:
|
||||
return None
|
||||
|
||||
# 简单的负载均衡:选择当前任务数最少的服务器
|
||||
return min(available_servers, key=lambda s: s.current_tasks)
|
||||
|
||||
async def allocate_server(self, name: str) -> bool:
|
||||
"""分配服务器资源"""
|
||||
async with self.lock:
|
||||
if name in self.servers:
|
||||
server = self.servers[name]
|
||||
if (server.status == ServerStatus.ONLINE and
|
||||
server.current_tasks < server.max_concurrent_tasks):
|
||||
server.current_tasks += 1
|
||||
if server.current_tasks >= server.max_concurrent_tasks:
|
||||
server.status = ServerStatus.BUSY
|
||||
return True
|
||||
return False
|
||||
|
||||
async def release_server(self, name: str) -> bool:
|
||||
"""释放服务器资源"""
|
||||
async with self.lock:
|
||||
if name in self.servers:
|
||||
server = self.servers[name]
|
||||
if server.current_tasks > 0:
|
||||
server.current_tasks -= 1
|
||||
if server.status == ServerStatus.BUSY and server.current_tasks < server.max_concurrent_tasks:
|
||||
server.status = ServerStatus.ONLINE
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_server_status(self, name: str) -> Optional[ComfyUIServerInfo]:
|
||||
"""获取服务器状态"""
|
||||
async with self.lock:
|
||||
return self.servers.get(name)
|
||||
|
||||
async def get_all_servers(self) -> List[ComfyUIServerInfo]:
|
||||
"""获取所有服务器信息"""
|
||||
async with self.lock:
|
||||
return list(self.servers.values())
|
||||
|
||||
def _check_capabilities(self, server_capabilities: Dict[str, any], required_capabilities: Dict[str, any]) -> bool:
|
||||
"""检查服务器是否满足能力要求"""
|
||||
for key, value in required_capabilities.items():
|
||||
if key not in server_capabilities:
|
||||
return False
|
||||
if isinstance(value, dict) and isinstance(server_capabilities[key], dict):
|
||||
if not self._check_capabilities(server_capabilities[key], value):
|
||||
return False
|
||||
elif server_capabilities[key] != value:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def _check_server_health(self, server: ComfyUIServerInfo) -> bool:
|
||||
"""检查服务器健康状态"""
|
||||
try:
|
||||
timeout = ClientTimeout(total=10)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.get(f"{server.http_url}/system_stats") as response:
|
||||
if response.status == 200:
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"服务器 {server.name} 健康检查失败: {e}")
|
||||
|
||||
return False
|
||||
|
||||
async def _health_check_loop(self):
|
||||
"""健康检查循环"""
|
||||
while True:
|
||||
try:
|
||||
await self._perform_health_checks()
|
||||
except Exception as e:
|
||||
logger.error(f"健康检查过程中发生错误: {e}")
|
||||
|
||||
await asyncio.sleep(self.health_check_interval)
|
||||
|
||||
async def _perform_health_checks(self):
|
||||
"""执行健康检查"""
|
||||
current_time = datetime.now()
|
||||
|
||||
async with self.lock:
|
||||
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} 心跳超时,标记为离线")
|
||||
|
||||
# 定期健康检查
|
||||
if (not server.last_health_check or
|
||||
current_time - server.last_health_check > timedelta(seconds=self.health_check_interval)):
|
||||
|
||||
is_healthy = await self._check_server_health(server)
|
||||
server.last_health_check = current_time
|
||||
|
||||
if is_healthy and server.status != ServerStatus.ONLINE:
|
||||
server.status = ServerStatus.ONLINE
|
||||
logger.info(f"服务器 {server.name} 恢复在线")
|
||||
elif not is_healthy and server.status == ServerStatus.ONLINE:
|
||||
server.status = ServerStatus.ERROR
|
||||
logger.warning(f"服务器 {server.name} 健康检查失败")
|
||||
|
||||
def _start_health_check(self):
|
||||
"""启动健康检查任务"""
|
||||
try:
|
||||
# 检查是否有运行中的事件循环
|
||||
loop = asyncio.get_running_loop()
|
||||
if self._health_check_task is None or self._health_check_task.done():
|
||||
self._health_check_task = asyncio.create_task(self._health_check_loop())
|
||||
except RuntimeError:
|
||||
# 没有运行中的事件循环,延迟启动
|
||||
logger.debug("没有运行中的事件循环,健康检查将在事件循环启动后自动开始")
|
||||
|
||||
async def _ensure_health_check_started(self):
|
||||
"""确保健康检查任务已启动"""
|
||||
if self._health_check_task is None or self._health_check_task.done():
|
||||
self._start_health_check()
|
||||
|
||||
async def shutdown(self):
|
||||
"""关闭管理器"""
|
||||
if self._health_check_task and not self._health_check_task.done():
|
||||
self._health_check_task.cancel()
|
||||
try:
|
||||
await self._health_check_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
|
||||
# 全局服务器管理器实例
|
||||
server_manager = ComfyUIServerManager()
|
||||
|
||||
|
||||
# 兼容性函数,用于替代原有的静态配置
|
||||
async def get_available_servers() -> List[ComfyUIServerInfo]:
|
||||
"""获取可用的服务器列表(兼容性函数)"""
|
||||
return await server_manager.get_all_servers()
|
||||
|
||||
|
||||
async def get_server_for_task(required_capabilities: Optional[Dict[str, any]] = None) -> Optional[ComfyUIServerInfo]:
|
||||
"""为任务获取合适的服务器(兼容性函数)"""
|
||||
return await server_manager.get_available_server(required_capabilities)
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
import aiohttp
|
||||
from workflow_service.config import ComfyUIServer
|
||||
from workflow_service.comfy.comfy_server import ComfyUIServerInfo
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
@@ -17,7 +17,7 @@ async def build_prompt(
|
||||
workflow_data: dict,
|
||||
api_spec: dict[str, dict[str, Any]],
|
||||
request_data: dict[str, Any],
|
||||
server: ComfyUIServer,
|
||||
server: ComfyUIServerInfo,
|
||||
):
|
||||
"""
|
||||
构建prompt
|
||||
@@ -190,7 +190,7 @@ async def _patch_workflow(
|
||||
workflow_data: dict,
|
||||
api_spec: dict[str, dict[str, Any]],
|
||||
request_data: dict[str, Any],
|
||||
server: ComfyUIServer,
|
||||
server: ComfyUIServerInfo,
|
||||
) -> dict:
|
||||
"""
|
||||
将request_data中的参数值,patch到workflow_data中。并返回修改后的workflow_data。
|
||||
@@ -326,7 +326,7 @@ def _convert_workflow_to_prompt_api_format(workflow_data: dict) -> dict:
|
||||
return prompt_api_format
|
||||
|
||||
|
||||
async def _upload_image_to_comfy(file_path: str, server: ComfyUIServer) -> str:
|
||||
async def _upload_image_to_comfy(file_path: str, server: ComfyUIServerInfo) -> str:
|
||||
"""
|
||||
上传文件到服务器。
|
||||
file 是一个http链接
|
||||
|
||||
@@ -20,11 +20,6 @@ class Settings(BaseSettings):
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
||||
|
||||
# [核心改动] 使用JSON字符串来定义一个服务器列表
|
||||
COMFYUI_SERVERS_JSON: str = (
|
||||
'[{"http_url": "http://127.0.0.1:8188", "ws_url": "ws://127.0.0.1:8188/ws", "input_dir": "/comfyui/input", "output_dir": "/comfyui/output"}]'
|
||||
)
|
||||
|
||||
DB_FILE: str = "/db/workflows_service.sqlite"
|
||||
# S3 和 AWS 配置保持不变
|
||||
S3_BUCKET_NAME: str
|
||||
@@ -32,21 +27,5 @@ class Settings(BaseSettings):
|
||||
AWS_SECRET_ACCESS_KEY: str
|
||||
AWS_REGION_NAME: str
|
||||
|
||||
@property
|
||||
def SERVERS(self) -> List[ComfyUIServer]:
|
||||
"""
|
||||
解析JSON配置字符串,返回一个经过验证的服务器配置对象列表。
|
||||
"""
|
||||
try:
|
||||
server_list = json.loads(self.COMFYUI_SERVERS_JSON)
|
||||
# 使用Pydantic模型进行验证和类型转换
|
||||
return [ComfyUIServer(**server) for server in server_list]
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError("COMFYUI_SERVERS_JSON 格式无效,必须是有效的JSON数组。")
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"COMFYUI_SERVERS_JSON 中的服务器配置无效: {e}")
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -4,9 +4,10 @@ import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from workflow_service.comfy.comfy_server import server_manager
|
||||
from workflow_service.database.api import init_db
|
||||
from workflow_service.config import Settings
|
||||
from workflow_service.routes import service, workflow, run, runx, monitor
|
||||
from workflow_service.routes import service, workflow, run, runx, monitor, comfy_server
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -26,23 +27,20 @@ web_app.include_router(workflow.workflow_router)
|
||||
web_app.include_router(run.run_router)
|
||||
web_app.include_router(runx.runx_router)
|
||||
web_app.include_router(monitor.monitor_router)
|
||||
web_app.include_router(comfy_server.router)
|
||||
|
||||
|
||||
@web_app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""服务启动时,初始化数据库并为所有配置的服务器创建输入/输出目录。"""
|
||||
"""服务启动时,初始化数据库。"""
|
||||
await init_db()
|
||||
try:
|
||||
servers = settings.SERVERS
|
||||
servers = await server_manager.get_all_servers()
|
||||
print(f"检测到 {len(servers)} 个 ComfyUI 服务器配置。")
|
||||
for server in servers:
|
||||
print(f" - 正在为服务器 {server.http_url} 准备目录...")
|
||||
os.makedirs(server.input_dir, exist_ok=True)
|
||||
print(f" - 输入目录: {os.path.abspath(server.input_dir)}")
|
||||
os.makedirs(server.output_dir, exist_ok=True)
|
||||
print(f" - 输出目录: {os.path.abspath(server.output_dir)}")
|
||||
print(f" - 服务器: {server.http_url}")
|
||||
except ValueError as e:
|
||||
print(f"错误: 无法在启动时初始化服务器目录: {e}")
|
||||
print(f"错误: 无法在启动时获取服务器信息: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
224
workflow_service/routes/comfy_server.py
Normal file
224
workflow_service/routes/comfy_server.py
Normal file
@@ -0,0 +1,224 @@
|
||||
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from typing import Dict, List, Optional, Any
|
||||
import logging
|
||||
|
||||
from workflow_service.comfy.comfy_server import (
|
||||
server_manager,
|
||||
ComfyUIServerInfo,
|
||||
ServerStatus,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/comfy", tags=["ComfyUI Server Management"])
|
||||
|
||||
|
||||
class ServerRegistrationRequest(BaseModel):
|
||||
"""服务器注册请求模型"""
|
||||
|
||||
name: str
|
||||
http_url: str
|
||||
ws_url: str
|
||||
|
||||
|
||||
class ServerHeartbeatRequest(BaseModel):
|
||||
"""服务器心跳请求模型"""
|
||||
|
||||
name: str
|
||||
|
||||
|
||||
class ServerUnregisterRequest(BaseModel):
|
||||
"""服务器注销请求模型"""
|
||||
|
||||
name: str
|
||||
|
||||
|
||||
class ServerStatusResponse(BaseModel):
|
||||
"""服务器状态响应模型"""
|
||||
|
||||
name: str
|
||||
http_url: str
|
||||
ws_url: str
|
||||
status: str
|
||||
last_heartbeat: Optional[str] = None
|
||||
last_health_check: Optional[str] = None
|
||||
current_tasks: int
|
||||
max_concurrent_tasks: int
|
||||
capabilities: Dict[str, Any]
|
||||
metadata: Dict[str, Any]
|
||||
|
||||
|
||||
@router.post("/register", response_model=Dict[str, str])
|
||||
async def register_server(request: ServerRegistrationRequest):
|
||||
"""注册ComfyUI服务器"""
|
||||
try:
|
||||
print(request)
|
||||
success = await server_manager.register_server(
|
||||
name=request.name,
|
||||
http_url=request.http_url,
|
||||
ws_url=request.ws_url,
|
||||
max_concurrent_tasks=1,
|
||||
capabilities={},
|
||||
metadata={},
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info(f"服务器 {request.name} 注册成功")
|
||||
return {"message": f"服务器 {request.name} 注册成功", "status": "success"}
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="服务器注册失败")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"注册服务器 {request.name} 时发生错误: {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])
|
||||
async def unregister_server(request: ServerUnregisterRequest):
|
||||
"""注销ComfyUI服务器"""
|
||||
try:
|
||||
success = await server_manager.unregister_server(request.name)
|
||||
|
||||
if success:
|
||||
logger.info(f"服务器 {request.name} 注销成功")
|
||||
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.get("/status/{server_name}", response_model=ServerStatusResponse)
|
||||
async def get_server_status(server_name: str):
|
||||
"""获取指定服务器状态"""
|
||||
try:
|
||||
server_info = await server_manager.get_server_status(server_name)
|
||||
|
||||
if server_info is None:
|
||||
raise HTTPException(status_code=404, detail=f"服务器 {server_name} 不存在")
|
||||
|
||||
return ServerStatusResponse(
|
||||
name=server_info.name,
|
||||
http_url=server_info.http_url,
|
||||
ws_url=server_info.ws_url,
|
||||
status=server_info.status.value,
|
||||
last_heartbeat=(
|
||||
server_info.last_heartbeat.isoformat()
|
||||
if server_info.last_heartbeat
|
||||
else None
|
||||
),
|
||||
last_health_check=(
|
||||
server_info.last_health_check.isoformat()
|
||||
if server_info.last_health_check
|
||||
else None
|
||||
),
|
||||
current_tasks=server_info.current_tasks,
|
||||
max_concurrent_tasks=server_info.max_concurrent_tasks,
|
||||
capabilities=server_info.capabilities,
|
||||
metadata=server_info.metadata,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取服务器 {server_name} 状态时发生错误: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"获取状态失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/list", response_model=List[ServerStatusResponse])
|
||||
async def list_all_servers():
|
||||
"""获取所有服务器列表"""
|
||||
try:
|
||||
servers = await server_manager.get_all_servers()
|
||||
|
||||
return [
|
||||
ServerStatusResponse(
|
||||
name=server.name,
|
||||
http_url=server.http_url,
|
||||
ws_url=server.ws_url,
|
||||
status=server.status.value,
|
||||
last_heartbeat=(
|
||||
server.last_heartbeat.isoformat() if server.last_heartbeat else None
|
||||
),
|
||||
last_health_check=(
|
||||
server.last_health_check.isoformat()
|
||||
if server.last_health_check
|
||||
else None
|
||||
),
|
||||
current_tasks=server.current_tasks,
|
||||
max_concurrent_tasks=server.max_concurrent_tasks,
|
||||
capabilities=server.capabilities,
|
||||
metadata=server.metadata,
|
||||
)
|
||||
for server in servers
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取服务器列表时发生错误: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"获取列表失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/health", response_model=Dict[str, Any])
|
||||
async def get_system_health():
|
||||
"""获取系统整体健康状态"""
|
||||
try:
|
||||
servers = await server_manager.get_all_servers()
|
||||
|
||||
total_servers = len(servers)
|
||||
online_servers = len([s for s in servers if s.status == ServerStatus.ONLINE])
|
||||
busy_servers = len([s for s in servers if s.status == ServerStatus.BUSY])
|
||||
offline_servers = len([s for s in servers if s.status == ServerStatus.OFFLINE])
|
||||
error_servers = len([s for s in servers if s.status == ServerStatus.ERROR])
|
||||
|
||||
total_tasks = sum(s.current_tasks for s in servers)
|
||||
total_capacity = sum(s.max_concurrent_tasks for s in servers)
|
||||
|
||||
return {
|
||||
"total_servers": total_servers,
|
||||
"online_servers": online_servers,
|
||||
"busy_servers": busy_servers,
|
||||
"offline_servers": offline_servers,
|
||||
"error_servers": error_servers,
|
||||
"current_tasks": total_tasks,
|
||||
"total_capacity": total_capacity,
|
||||
"utilization_rate": (
|
||||
(total_tasks / total_capacity * 100) if total_capacity > 0 else 0
|
||||
),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取系统健康状态时发生错误: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"获取健康状态失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/force_health_check", response_model=Dict[str, str])
|
||||
async def force_health_check(background_tasks: BackgroundTasks):
|
||||
"""强制执行健康检查"""
|
||||
try:
|
||||
# 在后台执行健康检查
|
||||
background_tasks.add_task(server_manager._perform_health_checks)
|
||||
return {"message": "健康检查已启动", "status": "success"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"启动健康检查时发生错误: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"启动健康检查失败: {str(e)}")
|
||||
@@ -2,13 +2,18 @@ from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
import psutil
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any, List
|
||||
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.comfy.comfy_queue import WorkflowQueueManager
|
||||
from workflow_service.comfy.comfy_server import server_manager
|
||||
|
||||
monitor_router = APIRouter(prefix="/monitor", tags=["监控"])
|
||||
|
||||
@@ -223,12 +228,24 @@ async def monitor_dashboard():
|
||||
const statusClass = server.status === 'online' ? 'online' : 'offline';
|
||||
const statusColor = server.status === 'online' ? 'status-online' : 'status-offline';
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return 'N/A';
|
||||
try {
|
||||
return new Date(timeStr).toLocaleString();
|
||||
} catch {
|
||||
return timeStr;
|
||||
}
|
||||
};
|
||||
|
||||
container.innerHTML += `
|
||||
<div class="server-card ${statusClass}">
|
||||
<h4>${server.http_url}</h4>
|
||||
<p><span class="status-indicator ${statusColor}"></span>${server.status}</p>
|
||||
<p><strong>输入目录:</strong> ${server.input_dir}</p>
|
||||
<p><strong>输出目录:</strong> ${server.output_dir}</p>
|
||||
<p><strong>任务状态:</strong> ${server.current_tasks || 0}/${server.max_concurrent_tasks || 1}</p>
|
||||
<p><strong>最后心跳:</strong> ${formatTime(server.last_heartbeat)}</p>
|
||||
<p><strong>最后检查:</strong> ${formatTime(server.last_health_check)}</p>
|
||||
${server.error ? `<p><strong>错误:</strong> <span style="color: red;">${server.error}</span></p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
@@ -324,6 +341,13 @@ async def get_server_status() -> List[Dict[str, Any]]:
|
||||
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)
|
||||
@@ -333,32 +357,38 @@ async def get_server_status() -> List[Dict[str, Any]]:
|
||||
return {
|
||||
"http_url": server.http_url,
|
||||
"ws_url": server.ws_url,
|
||||
"input_dir": server.input_dir,
|
||||
"output_dir": server.output_dir,
|
||||
"status": "online",
|
||||
"response_time": response.headers.get('X-Response-Time', 'N/A')
|
||||
"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 {
|
||||
"http_url": server.http_url,
|
||||
"ws_url": server.ws_url,
|
||||
"input_dir": server.input_dir,
|
||||
"output_dir": server.output_dir,
|
||||
"status": "offline",
|
||||
"error": f"HTTP {response.status}"
|
||||
"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)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"http_url": server.http_url,
|
||||
"ws_url": server.ws_url,
|
||||
"input_dir": server.input_dir,
|
||||
"output_dir": server.output_dir,
|
||||
"status": "offline",
|
||||
"error": str(e)
|
||||
"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)
|
||||
}
|
||||
|
||||
# 并发检查所有服务器状态
|
||||
tasks = [check_server_status(server) for server in settings.SERVERS]
|
||||
tasks = [check_server_status(server) for server in all_servers]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# 过滤掉异常结果
|
||||
@@ -409,15 +439,24 @@ async def health_check() -> Dict[str, Any]:
|
||||
from workflow_service.database.connection import get_db
|
||||
db = get_db()
|
||||
|
||||
# 检查配置
|
||||
servers_count = len(settings.SERVERS)
|
||||
# 从 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']
|
||||
|
||||
return {
|
||||
"status": "healthy",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"version": "1.0.0",
|
||||
"database": "connected",
|
||||
"servers_configured": servers_count,
|
||||
"servers": {
|
||||
"total_dynamic": dynamic_servers_count,
|
||||
"online": len(online_servers),
|
||||
"offline": len(offline_servers)
|
||||
},
|
||||
"uptime": "N/A" # 可以添加启动时间跟踪
|
||||
}
|
||||
except Exception as e:
|
||||
|
||||
@@ -8,6 +8,7 @@ from fastapi import APIRouter, HTTPException, Path
|
||||
from pydantic import BaseModel
|
||||
|
||||
from workflow_service.comfy.comfy_queue import queue_manager
|
||||
from workflow_service.comfy.comfy_server import server_manager
|
||||
from workflow_service.config import Settings
|
||||
|
||||
settings = Settings()
|
||||
@@ -28,8 +29,6 @@ class ServerStatus(BaseModel):
|
||||
server_index: int
|
||||
http_url: str
|
||||
ws_url: str
|
||||
input_dir: str
|
||||
output_dir: str
|
||||
is_reachable: bool
|
||||
is_free: bool
|
||||
queue_details: ServerQueueDetails
|
||||
@@ -72,7 +71,7 @@ async def get_servers_status():
|
||||
"""
|
||||
获取所有已配置的ComfyUI服务器的配置信息和实时状态。
|
||||
"""
|
||||
servers = settings.SERVERS
|
||||
servers = await server_manager.get_all_servers()
|
||||
if not servers:
|
||||
return []
|
||||
|
||||
@@ -90,8 +89,6 @@ async def get_servers_status():
|
||||
server_index=i,
|
||||
http_url=server_config.http_url,
|
||||
ws_url=server_config.ws_url,
|
||||
input_dir=server_config.input_dir,
|
||||
output_dir=server_config.output_dir,
|
||||
is_reachable=status_data["is_reachable"],
|
||||
is_free=status_data["is_free"],
|
||||
queue_details=status_data["queue_details"],
|
||||
@@ -106,8 +103,9 @@ async def list_server_files(
|
||||
):
|
||||
"""
|
||||
获取指定ComfyUI服务器的输入和输出文件夹中的文件列表。
|
||||
注意:由于节点管理不再维护目录,此接口返回空列表。
|
||||
"""
|
||||
servers = settings.SERVERS
|
||||
servers = await server_manager.get_all_servers()
|
||||
if server_index >= len(servers):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -116,10 +114,8 @@ async def list_server_files(
|
||||
|
||||
server_config = servers[server_index]
|
||||
|
||||
input_files, output_files = await asyncio.gather(
|
||||
_get_folder_contents(server_config.input_dir),
|
||||
_get_folder_contents(server_config.output_dir),
|
||||
)
|
||||
# 由于节点管理不再维护目录,返回空列表
|
||||
input_files, output_files = [], []
|
||||
|
||||
return ServerFiles(
|
||||
server_index=server_index,
|
||||
|
||||
Reference in New Issue
Block a user