From 5ae0ecae526d8d56aebd81aa14919bec59667ab7 Mon Sep 17 00:00:00 2001 From: jinchao <383321154@qq.com> Date: Thu, 29 May 2025 09:26:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Release/database/XNSim.db | Bin 1224704 -> 1224704 bytes XNSimHtml/components/overview-page.js | 4 +- XNSimHtml/components/run-test.js | 517 ++++++++++++-------------- XNSimHtml/routes/run-simulation.js | 91 ++++- 4 files changed, 322 insertions(+), 290 deletions(-) diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 798e3a9db810937bb70e55c8f793a822f1db1cf7..3081d587196d09200b28c7bbbf408ee9a3e35e33 100644 GIT binary patch delta 818 zcmZp8;MMTJYl1Z6vWYUzjLSAAEb(Vn=9@H`J;0TTZ_;K(1sA@_bK*FdjFp?`#BHAw z$M`ppk#qX24U8Kkj4Tuk&8&Xz4>_=RBde=S;fUUrzdS>+{O);H8h&8yoqr= zP}k*6j298Qj4d1ecz78k4Q(5hLAv&AX55L8H8E<8;)cjtgJgTRFdjviX=1iLb}M5d z6VTB8+mR(qjkovjU|jzVC}F>z3CR@(hTAqUu>|n2@HQ~;C-5EMQ{ZjbD!^R8%iAd3 zb|;8~p^?|NSek!&Avd%5^u1inGAbq(h89MarWR&;Ir-^E21cg32Bx}3mLUd)Rz}8F zrUrUu#wODP`I)ukGgA}{6?98di%W`1^YSwD(hY%T-p&_dRILtIKH!?KEYNmmOo{^~$*quU{eqYXbfm5JO zfw@hArA>jgO@XaVfxS(EqfLQxn*tZh1s)cjcMSXje1*JodENoj*F7GMMjd4iaYb3@ z#&mH*JtIA1L|_|OS{a!F{qSt|mS@v8OfK{m<$S(;_sf|}p6qIyUbvA_GN7UJS@+8K zdwQM&RrD==vUmUUDJxzs-1>6vqL&M|Jzu^4`K*>_^ID$H>wetO#0Av*v}*-W?~@%3 zPj=0IzPshg?rlI7Pxkgc-@7g`B{ws#eePc_Am#>Q9w6ogVm=_|2V#NkbN>n^Zvp_g C>JAnF delta 306 zcmZp8;MMTJYl1Z6w23m#jMFwIEb(Vn;+r#>J-~IdqCx;)^PITtbK)5P25z5ufbjy$ z_ROt}p-kKN9cQd%+-|*-k@*`V$8`JkOxw5(jT8)wtV|89OpUi~U}6d2VGv+o;Csz` zjprcunymuN1-#SeaWnHxujFC&W_s>1eIqxsy|S5^v8Ac8p官方网站:无 diff --git a/XNSimHtml/components/run-test.js b/XNSimHtml/components/run-test.js index 38edc87..3f2dc45 100644 --- a/XNSimHtml/components/run-test.js +++ b/XNSimHtml/components/run-test.js @@ -2,136 +2,117 @@ class RunTest extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); - this.scenarioFiles = []; - this.currentScenario = null; this.modelGroups = []; this.services = []; this.eventSource = null; + this.logFileWatcher = null; + this.logFilePollingInterval = null; } connectedCallback() { - this.fetchScenarioFiles(); this.render(); + this.loadModelGroups(); + this.loadServices(); } - async fetchScenarioFiles() { + async loadModelGroups() { try { - const response = await fetch('/api/scenario-files'); - if (!response.ok) { - throw new Error('无法获取场景文件'); + const savedSelection = localStorage.getItem('xnsim-selection'); + const selection = savedSelection ? JSON.parse(savedSelection) : {}; + const confID = selection.configurationId; + + if (!confID) { + this.showError('未选择构型'); + return; } - this.scenarioFiles = await response.json(); - this.updateScenarioDropdown(); - } catch (error) { - console.error('获取场景文件失败:', error); - this.showError('获取场景文件失败: ' + error.message); - } - } - updateScenarioDropdown() { - const dropdown = this.shadowRoot.querySelector('#scenario-select'); - if (!dropdown) return; - - // 清空现有选项 - dropdown.innerHTML = ''; - - // 添加提示选项 - const defaultOption = document.createElement('option'); - defaultOption.value = ''; - defaultOption.textContent = '-- 选择配置文件 --'; - defaultOption.disabled = true; - defaultOption.selected = true; - dropdown.appendChild(defaultOption); - - // 添加场景文件选项 - this.scenarioFiles.forEach(file => { - const option = document.createElement('option'); - option.value = file.path; - option.textContent = file.name; - dropdown.appendChild(option); - }); - } - - async onScenarioSelected(event) { - const selectedScenarioPath = event.target.value; - if (!selectedScenarioPath) { - return; - } - - try { - this.showMessage('加载配置文件内容...'); - // 使用file-content API获取XML内容 - const response = await fetch(`/api/file-content?path=${encodeURIComponent(selectedScenarioPath)}`); - + // 获取模型组列表 + const response = await fetch(`/api/configurations/${confID}/model-groups`); if (!response.ok) { - throw new Error('无法获取配置文件内容'); + throw new Error('获取模型组失败'); } - - const xmlContent = await response.text(); - this.parseScenarioXML(xmlContent); - - this.currentScenario = selectedScenarioPath; - this.updateModelAndServiceLists(); - this.showMessage('配置文件加载完成'); - } catch (error) { - console.error('加载配置文件失败:', error); - this.showError('加载配置文件失败: ' + error.message); - } - } - - parseScenarioXML(xmlContent) { - try { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(xmlContent, "text/xml"); - - // 解析模型组和模型 + const groups = await response.json(); + + // 获取每个模型组下的模型 this.modelGroups = []; - const modelGroupElements = xmlDoc.querySelectorAll('ModelGroup'); - modelGroupElements.forEach(groupElem => { - const groupName = groupElem.getAttribute('Name'); - const models = []; - - groupElem.querySelectorAll('Model').forEach(modelElem => { - models.push({ - name: modelElem.getAttribute('Name'), - className: modelElem.getAttribute('ClassName') - }); - }); - + for (const group of groups) { + const modelsResponse = await fetch(`/api/model-groups/${group.GroupID}/models`); + if (!modelsResponse.ok) { + throw new Error(`获取模型组 ${group.GroupName} 的模型失败`); + } + const models = await modelsResponse.json(); + this.modelGroups.push({ - name: groupName, - models: models + name: group.GroupName, + groupId: group.GroupID, + freq: group.Freq, + priority: group.Priority, + cpuAff: group.CPUAff, + models: models.map(model => ({ + className: model.ClassName, + version: model.ModelVersion + })) }); - }); - - // 解析服务 - this.services = []; - const serviceElements = xmlDoc.querySelectorAll('ServicesList > Service'); - serviceElements.forEach(serviceElem => { - this.services.push({ - name: serviceElem.getAttribute('Name'), - className: serviceElem.getAttribute('ClassName') - }); - }); + } + + this.updateModelList(); } catch (error) { - console.error('解析XML失败:', error); - throw new Error('解析XML失败: ' + error.message); + console.error('加载模型组失败:', error); + this.showError('加载模型组失败: ' + error.message); } } - - updateModelAndServiceLists() { - // 更新模型列表 + + async loadServices() { + try { + const savedSelection = localStorage.getItem('xnsim-selection'); + const selection = savedSelection ? JSON.parse(savedSelection) : {}; + const confID = selection.configurationId; + + if (!confID) { + this.showError('未选择构型'); + return; + } + + // 获取服务列表 + const response = await fetch(`/api/configurations/${confID}/services`); + if (!response.ok) { + throw new Error('获取服务列表失败'); + } + const services = await response.json(); + + this.services = services.map(service => ({ + className: service.ClassName, + version: service.ServiceVersion + })); + + this.updateServiceList(); + } catch (error) { + console.error('加载服务失败:', error); + this.showError('加载服务失败: ' + error.message); + } + } + + updateModelList() { const modelListContainer = this.shadowRoot.querySelector('#model-list'); + if (!modelListContainer) return; + modelListContainer.innerHTML = ''; this.modelGroups.forEach(group => { const groupItem = document.createElement('div'); groupItem.className = 'list-group'; + // 组名 const groupHeader = document.createElement('div'); groupHeader.className = 'list-group-header'; groupHeader.textContent = group.name; groupItem.appendChild(groupHeader); + + // 组信息(以小字显示) + const groupInfo = document.createElement('div'); + groupInfo.className = 'list-group-info'; + groupInfo.textContent = `频率:${group.freq}.0 Hz / 优先级:${group.priority} / 亲和性:${group.cpuAff}`; + groupItem.appendChild(groupInfo); const modelsList = document.createElement('div'); modelsList.className = 'list-items'; @@ -139,32 +120,33 @@ class RunTest extends HTMLElement { group.models.forEach(model => { const modelItem = document.createElement('div'); modelItem.className = 'list-item'; - modelItem.textContent = model.name; + modelItem.textContent = `${model.className} (v${model.version})`; modelsList.appendChild(modelItem); }); groupItem.appendChild(modelsList); modelListContainer.appendChild(groupItem); }); - - // 更新服务列表 + } + + updateServiceList() { const serviceListContainer = this.shadowRoot.querySelector('#service-list'); + if (!serviceListContainer) return; + serviceListContainer.innerHTML = ''; this.services.forEach(service => { const serviceItem = document.createElement('div'); serviceItem.className = 'list-item'; - serviceItem.textContent = service.name; + serviceItem.textContent = `${service.className} (v${service.version})`; serviceListContainer.appendChild(serviceItem); }); } async runTest() { - const selectedScenario = this.shadowRoot.querySelector('#scenario-select').value; - if (!selectedScenario) { - this.showError('请先选择配置文件'); - return; - } + const savedSelection = localStorage.getItem('xnsim-selection'); + const selection = savedSelection ? JSON.parse(savedSelection) : {}; + const confID = selection.configurationId; try { this.showMessage('准备运行测试...'); @@ -176,14 +158,8 @@ class RunTest extends HTMLElement { const outputContent = this.shadowRoot.querySelector('#output-content'); outputContent.innerHTML = '开始执行测试...\n'; - // 关闭之前的EventSource连接 - if (this.eventSource) { - this.eventSource.close(); - this.eventSource = null; - } - // 准备启动参数 - const simulationArgs = ['-f', selectedScenario, '-test']; + const simulationArgs = ['-id', confID, '-test']; // 调用后端API执行测试 const response = await fetch('/api/run-simulation', { @@ -207,8 +183,12 @@ class RunTest extends HTMLElement { const responseData = await response.json(); - // 连接到SSE获取实时输出 - this.connectToEventSource(responseData.simulationId); + // 获取进程ID和日志文件路径 + const processId = responseData.simulationId; + const logFile = responseData.logFile; + + // 开始轮询日志文件 + this.startLogFilePolling(logFile); // 根据测试结果更新UI if (responseData.success) { @@ -232,92 +212,79 @@ class RunTest extends HTMLElement { } } - // 连接到SSE事件源获取实时输出 - connectToEventSource(simulationId) { - // 关闭之前的连接 - if (this.eventSource) { - this.eventSource.close(); + // 开始轮询日志文件 + startLogFilePolling(logFile) { + // 清除之前的轮询 + if (this.logFilePollingInterval) { + clearInterval(this.logFilePollingInterval); + this.logFilePollingInterval = null; } - - // 创建新的SSE连接 - const url = `/api/simulation-output/${simulationId}`; - this.eventSource = new EventSource(url); - - // 标准输出和错误输出 - this.eventSource.addEventListener('output', (event) => { + + let lastPosition = 0; + const outputContent = this.shadowRoot.querySelector('#output-content'); + + // 每100ms检查一次文件 + this.logFilePollingInterval = setInterval(async () => { try { - const data = JSON.parse(event.data); - const outputContent = this.shadowRoot.querySelector('#output-content'); + // 检查进程是否还在运行 + const response = await fetch(`/api/check-process/${logFile.split('_')[1].split('.')[0]}`); + const data = await response.json(); - // 添加新输出并应用ANSI颜色 - if (data.data) { - const html = this.convertAnsiToHtml(data.data); - outputContent.innerHTML += html; + if (!data.running) { + // 进程已结束,读取剩余内容并停止轮询 + const finalContent = await this.readLogFile(logFile, lastPosition); + if (finalContent) { + outputContent.innerHTML += this.convertAnsiToHtml(finalContent); + outputContent.scrollTop = outputContent.scrollHeight; + } - // 自动滚动到底部 + clearInterval(this.logFilePollingInterval); + this.logFilePollingInterval = null; + + if (data.success) { + this.showSuccess('测试执行成功'); + } else { + this.showError(`测试执行失败: ${data.message || '未知错误'}`); + } + return; + } + + // 读取新的日志内容 + const content = await this.readLogFile(logFile, lastPosition); + if (content) { + outputContent.innerHTML += this.convertAnsiToHtml(content); outputContent.scrollTop = outputContent.scrollHeight; + lastPosition += content.length; } } catch (error) { - console.error('处理输出事件失败:', error); + console.error('读取日志文件失败:', error); + clearInterval(this.logFilePollingInterval); + this.logFilePollingInterval = null; + this.showError('读取日志文件失败: ' + error.message); } - }); - - // 仿真状态 - this.eventSource.addEventListener('status', (event) => { - try { - const data = JSON.parse(event.data); - - if (data.running === false) { - // 测试已经结束 - this.showMessage(data.message || '测试已结束'); - this.closeEventSource(); - } - } catch (error) { - console.error('处理状态事件失败:', error); - } - }); - - // 仿真完成 - this.eventSource.addEventListener('completed', (event) => { - try { - const data = JSON.parse(event.data); - - if (data.success) { - this.showSuccess('测试执行成功'); - } else { - this.showError(`测试执行失败: ${data.message}`); - } - - this.closeEventSource(); - } catch (error) { - console.error('处理完成事件失败:', error); - } - }); - - // 仿真错误 - this.eventSource.addEventListener('error', (event) => { - try { - const data = JSON.parse(event.data); - this.showError(`测试错误: ${data.message}`); - this.closeEventSource(); - } catch (error) { - console.error('处理错误事件失败:', error); - } - }); - - // 连接错误处理 - this.eventSource.onerror = (error) => { - console.error('SSE连接错误:', error); - this.showError('实时输出连接已断开'); - this.closeEventSource(); - }; + }, 100); } - - // 关闭SSE连接 - closeEventSource() { - if (this.eventSource) { - this.eventSource.close(); - this.eventSource = null; + + // 读取日志文件 + async readLogFile(logFile, startPosition) { + try { + const response = await fetch(`/api/read-log-file?file=${encodeURIComponent(logFile)}&position=${startPosition}`); + if (!response.ok) { + throw new Error('读取日志文件失败'); + } + const data = await response.json(); + return data.content; + } catch (error) { + console.error('读取日志文件失败:', error); + return null; + } + } + + // 在组件销毁时清理资源 + disconnectedCallback() { + if (this.logFilePollingInterval) { + clearInterval(this.logFilePollingInterval); + this.logFilePollingInterval = null; } } @@ -360,52 +327,6 @@ class RunTest extends HTMLElement { display: flex; flex-direction: column; } - - .config-selector { - display: flex; - align-items: center; - margin-bottom: 20px; - gap: 10px; - } - - .selector-label { - min-width: 120px; - font-weight: bold; - color: #333; - } - - select { - flex: 1; - padding: 8px 12px; - border-radius: 4px; - border: 1px solid #ccc; - } - - button { - padding: 8px 16px; - background-color: #1976d2; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-weight: bold; - min-width: 100px; - } - - button:hover { - background-color: #1565c0; - } - - button:disabled { - background-color: #cccccc; - cursor: not-allowed; - } - - #message { - margin: 10px 0; - padding: 10px; - border-radius: 4px; - } .content-container { display: flex; @@ -415,7 +336,7 @@ class RunTest extends HTMLElement { } .lists-container { - width: 250px; + width: 320px; display: flex; flex-direction: column; gap: 15px; @@ -450,9 +371,14 @@ class RunTest extends HTMLElement { .list-group-header { font-weight: bold; color: #333; - margin-bottom: 5px; - padding-bottom: 3px; - border-bottom: 1px solid #eee; + margin-bottom: 2px; + } + + .list-group-info { + font-size: 0.85em; + color: #666; + margin-bottom: 8px; + padding-left: 2px; } .list-items { @@ -477,6 +403,15 @@ class RunTest extends HTMLElement { padding: 10px; font-weight: bold; border-bottom: 1px solid #e0e0e0; + display: flex; + justify-content: space-between; + align-items: center; + } + + #message { + flex: 1; + text-align: center; + margin: 0 10px; } .output-content { @@ -489,48 +424,61 @@ class RunTest extends HTMLElement { white-space: pre-wrap; line-height: 1.4; } + + /* ANSI颜色样式 */ + .ansi-black { color: #000000; } + .ansi-red { color: #ff0000; } + .ansi-green { color: #00ff00; } + .ansi-yellow { color: #ffff00; } + .ansi-blue { color: #0000ff; } + .ansi-magenta { color: #ff00ff; } + .ansi-cyan { color: #00ffff; } + .ansi-white { color: #ffffff; } - /* 终端颜色支持 */ - .output-content .ansi-black { color: #000000; } - .output-content .ansi-red { color: #ff0000; } - .output-content .ansi-green { color: #00ff00; } - .output-content .ansi-yellow { color: #ffff00; } - .output-content .ansi-blue { color: #0000ff; } - .output-content .ansi-magenta { color: #ff00ff; } - .output-content .ansi-cyan { color: #00ffff; } - .output-content .ansi-white { color: #ffffff; } + .ansi-bright-black { color: #666666; } + .ansi-bright-red { color: #ff6666; } + .ansi-bright-green { color: #66ff66; } + .ansi-bright-yellow { color: #ffff66; } + .ansi-bright-blue { color: #6666ff; } + .ansi-bright-magenta { color: #ff66ff; } + .ansi-bright-cyan { color: #66ffff; } + .ansi-bright-white { color: #ffffff; } - .output-content .ansi-bright-black { color: #808080; } - .output-content .ansi-bright-red { color: #ff5555; } - .output-content .ansi-bright-green { color: #55ff55; } - .output-content .ansi-bright-yellow { color: #ffff55; } - .output-content .ansi-bright-blue { color: #5555ff; } - .output-content .ansi-bright-magenta { color: #ff55ff; } - .output-content .ansi-bright-cyan { color: #55ffff; } - .output-content .ansi-bright-white { color: #ffffff; } + .ansi-bg-black { background-color: #000000; } + .ansi-bg-red { background-color: #ff0000; } + .ansi-bg-green { background-color: #00ff00; } + .ansi-bg-yellow { background-color: #ffff00; } + .ansi-bg-blue { background-color: #0000ff; } + .ansi-bg-magenta { background-color: #ff00ff; } + .ansi-bg-cyan { background-color: #00ffff; } + .ansi-bg-white { background-color: #ffffff; } - .output-content .ansi-bg-black { background-color: #000000; } - .output-content .ansi-bg-red { background-color: #ff0000; } - .output-content .ansi-bg-green { background-color: #00ff00; } - .output-content .ansi-bg-yellow { background-color: #ffff00; } - .output-content .ansi-bg-blue { background-color: #0000ff; } - .output-content .ansi-bg-magenta { background-color: #ff00ff; } - .output-content .ansi-bg-cyan { background-color: #00ffff; } - .output-content .ansi-bg-white { background-color: #ffffff; } + .ansi-bold { font-weight: bold; } + .ansi-italic { font-style: italic; } + .ansi-underline { text-decoration: underline; } + + button { + padding: 6px 12px; + background-color: #1976d2; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-weight: bold; + min-width: 80px; + font-size: 0.9em; + } + + button:hover { + background-color: #1565c0; + } - .output-content .ansi-bold { font-weight: bold; } - .output-content .ansi-italic { font-style: italic; } - .output-content .ansi-underline { text-decoration: underline; } + button:disabled { + background-color: #cccccc; + cursor: not-allowed; + }
-
-
选择运行环境配置:
- - -
-
@@ -543,7 +491,11 @@ class RunTest extends HTMLElement {
-
运行输出
+
+ 运行输出 +
+ +
等待测试运行...
@@ -553,9 +505,6 @@ class RunTest extends HTMLElement { // 添加事件监听器 const runButton = this.shadowRoot.querySelector('#run-button'); runButton.addEventListener('click', () => this.runTest()); - - const scenarioSelect = this.shadowRoot.querySelector('#scenario-select'); - scenarioSelect.addEventListener('change', (event) => this.onScenarioSelected(event)); } // ANSI终端颜色转换为HTML diff --git a/XNSimHtml/routes/run-simulation.js b/XNSimHtml/routes/run-simulation.js index fdb9bc4..aa6ac25 100644 --- a/XNSimHtml/routes/run-simulation.js +++ b/XNSimHtml/routes/run-simulation.js @@ -2,7 +2,8 @@ const express = require('express'); const router = express.Router(); const { spawn, exec } = require('child_process'); const path = require('path'); -const fs = require('fs').promises; +const fs = require('fs'); +const fsPromises = require('fs').promises; const util = require('util'); const execPromise = util.promisify(exec); const { @@ -237,7 +238,7 @@ router.post('/run-simulation', async (req, res) => { if (!existingProcess) { // 创建日志文件 const logDir = path.join(process.cwd(), 'logs'); - await fs.mkdir(logDir, { recursive: true }); + await fsPromises.mkdir(logDir, { recursive: true }); const logFile = path.join(logDir, `xnengine_${mainProcess.pid}.log`); // 从命令行参数中提取配置文件路径 @@ -288,7 +289,7 @@ router.post('/run-simulation', async (req, res) => { // 检查引擎程序是否存在 try { - await fs.access(enginePath); + await fsPromises.access(enginePath); } catch (error) { return res.status(404).json({ error: 'XNEngine不存在', @@ -298,7 +299,7 @@ router.post('/run-simulation', async (req, res) => { // 创建日志文件 const logDir = path.join(process.cwd(), 'logs'); - await fs.mkdir(logDir, { recursive: true }); + await fsPromises.mkdir(logDir, { recursive: true }); const logFile = path.join(logDir, `xnengine_${simulationId}.log`); // 使用nohup启动进程,将输出重定向到日志文件 @@ -504,4 +505,86 @@ router.post('/cleanup-simulation/:id', (req, res) => { res.json({ success: true, message: '仿真资源已清理' }); }); +// 添加读取日志文件的路由 +router.get('/read-log-file', async (req, res) => { + try { + const { file, position } = req.query; + if (!file) { + return res.status(400).json({ error: '缺少日志文件路径' }); + } + + const startPosition = parseInt(position) || 0; + + // 读取文件 + const stats = await fsPromises.stat(file); + if (stats.size < startPosition) { + // 文件被截断,从头开始读取 + startPosition = 0; + } + + // 使用二进制模式读取,以保留ANSI颜色代码 + const stream = fs.createReadStream(file, { + start: startPosition, + encoding: null // 使用二进制模式 + }); + + let content = ''; + for await (const chunk of stream) { + // 将二进制数据转换为字符串,保留ANSI颜色代码 + content += chunk.toString('utf8'); + } + + res.json({ content }); + } catch (error) { + console.error('读取日志文件失败:', error); + res.status(500).json({ + error: '读取日志文件失败', + message: error.message + }); + } +}); + +// 添加检查进程状态的路由 +router.get('/check-process/:pid', async (req, res) => { + try { + const pid = req.params.pid; + if (!pid) { + return res.status(400).json({ error: '缺少进程ID' }); + } + + // 检查进程是否在运行 + const isRunning = await isProcessRunning(pid); + const isXNEngine = await isXNEngineProcess(pid); + + if (isRunning && isXNEngine) { + res.json({ running: true }); + } else { + // 进程已结束,检查日志文件是否有错误 + const logFile = path.join(process.cwd(), 'logs', `xnengine_${pid}.log`); + try { + const content = await fsPromises.readFile(logFile, 'utf8'); + const hasError = content.includes('Error:') || content.includes('error:'); + + res.json({ + running: false, + success: !hasError, + message: hasError ? '测试执行失败' : '测试执行成功' + }); + } catch (error) { + res.json({ + running: false, + success: false, + message: '无法读取日志文件' + }); + } + } + } catch (error) { + console.error('检查进程状态失败:', error); + res.status(500).json({ + error: '检查进程状态失败', + message: error.message + }); + } +}); + module.exports = router; \ No newline at end of file