feat: 为项目添加 WebUI

This commit is contained in:
foxhui
2025-12-19 04:43:59 +08:00
parent 1ccaa55608
commit 41e3e022a3
33 changed files with 1842 additions and 40 deletions
+356
View File
@@ -0,0 +1,356 @@
/**
* @fileoverview Admin API 路由模块
* @description 提供管理接口,包括系统状态、配置管理、适配器元数据等
*/
import { sendJson, sendApiError } from './respond.js';
import { ERROR_CODES } from '../errors.js';
import { logger } from '../../utils/logger.js';
import {
getSystemStatus,
getDataFolders,
deleteDataFolders,
clearTempFiles
} from '../../utils/systemInfo.js';
import {
getServerConfig,
saveServerConfig,
getBrowserConfig,
saveBrowserConfig,
getQueueConfig,
saveQueueConfig,
getInstancesConfig,
saveInstancesConfig,
getAdaptersConfig,
saveAdaptersConfig,
getPoolConfig,
savePoolConfig
} from '../../utils/configManager.js';
import {
validateServerConfig,
validateBrowserConfig,
validateInstancesConfig,
validatePoolConfig,
validateAdaptersConfig
} from '../../utils/configValidator.js';
import { registry } from '../../backend/registry.js';
/**
* 读取请求体
* @param {import('http').IncomingMessage} req
* @returns {Promise<object>}
*/
async function readBody(req) {
const chunks = [];
for await (const chunk of req) {
chunks.push(chunk);
}
const body = Buffer.concat(chunks).toString();
return body ? JSON.parse(body) : {};
}
/**
* 创建 Admin 路由处理器
* @param {object} context - 路由上下文
* @param {object} context.config - 完整配置对象
* @param {object} context.queueManager - 队列管理器
* @param {string} context.tempDir - 临时目录
* @returns {Function} Admin 路由处理函数
*/
export function createAdminRouter(context) {
const { config, queueManager, tempDir } = context;
/**
* Admin 路由处理函数
* @param {import('http').IncomingMessage} req
* @param {import('http').ServerResponse} res
* @param {string} pathname - 去除 /admin 前缀后的路径
*/
return async function handleAdminRequest(req, res, pathname) {
const method = req.method;
try {
// ==================== 系统管理 ====================
// GET /admin/status - 系统状态
if (method === 'GET' && pathname === '/status') {
const status = getSystemStatus();
sendJson(res, 200, status);
return;
}
// POST /admin/restart - 重启服务(使用子进程分离)
if (method === 'POST' && pathname === '/restart') {
const { spawn } = await import('child_process');
sendJson(res, 200, { success: true, message: '服务正在重启...' });
logger.info('管理器', '收到重启请求,将在 1 秒后重启');
setTimeout(() => {
// 启动新进程(完全独立)
const child = spawn(process.execPath, process.argv.slice(1), {
cwd: process.cwd(),
detached: true,
stdio: 'ignore',
env: process.env
});
child.unref();
// 退出当前进程
setTimeout(() => process.exit(0), 500);
}, 1000);
return;
}
// POST /admin/stop - 停止服务
if (method === 'POST' && pathname === '/stop') {
sendJson(res, 200, { success: true, message: '服务正在停止...' });
logger.info('管理器', '收到停止请求,将在 1 秒后退出');
setTimeout(() => process.exit(0), 1000);
return;
}
// ==================== 缓存与数据管理 ====================
// POST /admin/cache/clear - 清理缓存
if (method === 'POST' && pathname === '/cache/clear') {
const result = clearTempFiles(tempDir);
sendJson(res, 200, { success: true, cleaned: result.cleaned });
return;
}
// GET /admin/data-folders - 列出数据文件夹
if (method === 'GET' && pathname === '/data-folders') {
const workers = config.backend?.pool?.workers || [];
const folders = getDataFolders(workers);
sendJson(res, 200, folders);
return;
}
// POST /admin/data-folders/delete - 删除数据文件夹
if (method === 'POST' && pathname === '/data-folders/delete') {
const body = await readBody(req);
if (!body.folders || !Array.isArray(body.folders)) {
sendApiError(res, { code: ERROR_CODES.INVALID_REQUEST_BODY, message: '缺少 folders 数组' });
return;
}
const workers = config.backend?.pool?.workers || [];
const result = deleteDataFolders(body.folders, workers);
if (result.errors.length > 0) {
sendJson(res, 207, result); // 207 Multi-Status
} else {
sendJson(res, 200, result);
}
return;
}
// ==================== 配置管理 ====================
// GET/POST /admin/config/server
if (pathname === '/config/server') {
if (method === 'GET') {
const serverConfig = getServerConfig();
const queueConfig = getQueueConfig();
sendJson(res, 200, {
...serverConfig,
queueBuffer: queueConfig.queueBuffer,
imageLimit: queueConfig.imageLimit
});
} else if (method === 'POST') {
const body = await readBody(req);
// 校验配置
const validation = validateServerConfig(body);
if (!validation.valid) {
sendApiError(res, {
code: ERROR_CODES.INVALID_REQUEST_BODY,
message: `配置校验失败: ${validation.errors.join('; ')}`
});
return;
}
// 分别保存 server 和 queue 配置
saveServerConfig(body);
if (body.queueBuffer !== undefined || body.imageLimit !== undefined) {
saveQueueConfig(body);
}
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
} else {
res.writeHead(405);
res.end();
}
return;
}
// GET/POST /admin/config/browser
if (pathname === '/config/browser') {
if (method === 'GET') {
sendJson(res, 200, getBrowserConfig());
} else if (method === 'POST') {
const body = await readBody(req);
// 校验配置
const validation = validateBrowserConfig(body);
if (!validation.valid) {
sendApiError(res, {
code: ERROR_CODES.INVALID_REQUEST_BODY,
message: `配置校验失败: ${validation.errors.join('; ')}`
});
return;
}
saveBrowserConfig(body);
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
} else {
res.writeHead(405);
res.end();
}
return;
}
// GET/POST /admin/config/instances (对应原设计的 workers)
if (pathname === '/config/instances' || pathname === '/config/workers') {
if (method === 'GET') {
sendJson(res, 200, getInstancesConfig());
} else if (method === 'POST') {
const body = await readBody(req);
// 校验配置(包括 Instance/Worker 名称唯一性)
const validation = validateInstancesConfig(body);
if (!validation.valid) {
sendApiError(res, {
code: ERROR_CODES.INVALID_REQUEST_BODY,
message: `配置校验失败: ${validation.errors.join('; ')}`
});
return;
}
saveInstancesConfig(body);
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
} else {
res.writeHead(405);
res.end();
}
return;
}
// GET/POST /admin/config/adapters
if (pathname === '/config/adapters') {
if (method === 'GET') {
sendJson(res, 200, getAdaptersConfig());
} else if (method === 'POST') {
const body = await readBody(req);
// 校验配置
const validation = validateAdaptersConfig(body);
if (!validation.valid) {
sendApiError(res, {
code: ERROR_CODES.INVALID_REQUEST_BODY,
message: `配置校验失败: ${validation.errors.join('; ')}`
});
return;
}
saveAdaptersConfig(body);
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
} else {
res.writeHead(405);
res.end();
}
return;
}
// GET/POST /admin/config/pool - 负载均衡和故障转移配置
if (pathname === '/config/pool') {
if (method === 'GET') {
sendJson(res, 200, getPoolConfig());
} else if (method === 'POST') {
const body = await readBody(req);
// 校验配置
const validation = validatePoolConfig(body);
if (!validation.valid) {
sendApiError(res, {
code: ERROR_CODES.INVALID_REQUEST_BODY,
message: `配置校验失败: ${validation.errors.join('; ')}`
});
return;
}
savePoolConfig(body);
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
} else {
res.writeHead(405);
res.end();
}
return;
}
// ==================== 元数据 ====================
// GET /admin/adapters - 获取适配器列表(含 configSchema
if (method === 'GET' && pathname === '/adapters') {
const adapters = [];
const adapterIds = registry.getAdapterIds();
for (const id of adapterIds) {
const adapter = registry.getAdapter(id);
if (adapter) {
adapters.push({
id: adapter.id,
displayName: adapter.displayName || adapter.id,
modelCount: adapter.models?.length || 0,
models: (adapter.models || []).map(m => m.id),
configSchema: adapter.configSchema || []
});
}
}
sendJson(res, 200, adapters);
return;
}
// ==================== 统计与监控 ====================
// GET /admin/stats - 基本统计
if (method === 'GET' && pathname === '/stats') {
const instances = config.backend?.pool?.instances || [];
const workers = config.backend?.pool?.workers || [];
sendJson(res, 200, {
instances: instances.length,
workers: workers.length
});
return;
}
// GET /admin/queue - 任务队列状态
if (method === 'GET' && pathname === '/queue') {
const queueStatus = queueManager.getStatus();
const detailedStatus = queueManager.getDetailedStatus();
sendJson(res, 200, {
processing: queueStatus.processing,
waiting: queueStatus.queueLength,
total: queueStatus.total,
processingTasks: detailedStatus.processing,
waitingTasks: detailedStatus.waiting
});
return;
}
// 404
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not Found' }));
} catch (err) {
logger.error('管理器', `请求处理失败: ${err.message}`);
sendApiError(res, {
code: ERROR_CODES.INTERNAL_ERROR,
message: err.message
});
}
};
}