class RunSim extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.modelGroups = []; this.services = []; this.eventSource = null; this.logFileWatcher = null; this.logFilePollingInterval = null; this.currentSimulationId = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 3; } connectedCallback() { this.render(); this.loadModelGroups(); this.loadServices(); // 检查并连接到已有的仿真 this.checkAndConnectToExistingSimulation(); } async loadModelGroups() { 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}/model-groups`); if (!response.ok) { throw new Error('获取模型组失败'); } const groups = await response.json(); // 获取每个模型组下的模型 this.modelGroups = []; 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: 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.updateModelList(); } catch (error) { console.error('加载模型组失败:', error); this.showError('加载模型组失败: ' + error.message); } } 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'; group.models.forEach(model => { const modelItem = document.createElement('div'); modelItem.className = 'list-item'; 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.className} (v${service.version})`; serviceListContainer.appendChild(serviceItem); }); } async runTest() { const savedSelection = localStorage.getItem('xnsim-selection'); const selection = savedSelection ? JSON.parse(savedSelection) : {}; const confID = selection.configurationId; try { this.showMessage('准备运行测试...'); const runButton = this.shadowRoot.querySelector('#run-button'); runButton.disabled = true; runButton.textContent = '运行中...'; // 清空并初始化输出框 const outputContent = this.shadowRoot.querySelector('#output-content'); outputContent.innerHTML = '开始执行测试...\n'; // 准备启动参数 const simulationArgs = ['-id', confID, '-test']; // 调用后端API执行测试 const response = await fetch('/api/run-simulation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ args: simulationArgs, timeout: 120000 // 2分钟超时 }) }); if (!response.ok) { const errorData = await response.json(); outputContent.innerHTML += `\n错误: ${errorData.message || '测试执行失败'}\n`; if (errorData.output) outputContent.innerHTML += this.convertAnsiToHtml(errorData.output); if (errorData.errorOutput) outputContent.innerHTML += this.convertAnsiToHtml(errorData.errorOutput); throw new Error(errorData.message || '测试执行失败'); } const responseData = await response.json(); // 获取进程ID和日志文件路径 const processId = responseData.simulationId; const logFile = responseData.logFile; // 开始轮询日志文件 this.startLogFilePolling(logFile); // 根据测试结果更新UI if (responseData.success) { this.showSuccess('测试已启动'); } else { this.showError(`测试启动失败: ${responseData.message || '未知错误'}`); } } catch (error) { console.error('执行测试失败:', error); this.showError('执行测试失败: ' + error.message); // 显示错误详情 const outputContent = this.shadowRoot.querySelector('#output-content'); outputContent.innerHTML += `\n\n执行错误: ${error.message}`; } finally { // 重置UI const runButton = this.shadowRoot.querySelector('#run-button'); runButton.disabled = false; runButton.textContent = '运行测试'; } } // 开始轮询日志文件 startLogFilePolling(logFile) { // 清除之前的轮询 if (this.logFilePollingInterval) { clearInterval(this.logFilePollingInterval); this.logFilePollingInterval = null; } let lastPosition = 0; const outputContent = this.shadowRoot.querySelector('#output-content'); // 每100ms检查一次文件 this.logFilePollingInterval = setInterval(async () => { try { // 检查进程是否还在运行 const response = await fetch(`/api/check-process/${logFile.split('_')[1].split('.')[0]}`); const data = await response.json(); 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); clearInterval(this.logFilePollingInterval); this.logFilePollingInterval = null; this.showError('读取日志文件失败: ' + error.message); } }, 100); } // 读取日志文件 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; } } // 连接到SSE事件源获取实时输出 connectToEventSource(simulationId) { // 关闭之前的连接 if (this.eventSource) { this.eventSource.close(); } // 创建新的SSE连接 const url = `/api/simulation-output/${simulationId}`; this.eventSource = new EventSource(url); // 标准输出和错误输出 this.eventSource.addEventListener('output', (event) => { try { const data = JSON.parse(event.data); const outputContent = this.shadowRoot.querySelector('#output-content'); // 添加新输出并应用ANSI颜色 if (data.data) { const html = this.convertAnsiToHtml(data.data); outputContent.innerHTML += html; // 自动滚动到底部 outputContent.scrollTop = outputContent.scrollHeight; } } catch (error) { console.error('处理输出事件失败:', error); } }); // 仿真状态 this.eventSource.addEventListener('status', (event) => { try { const data = JSON.parse(event.data); if (data.running === false) { // 仿真已经不存在或已结束 this.showMessage(data.message || '仿真不存在或已结束'); // 如果是进程不存在,尝试重新连接 if (data.message === '仿真不存在或已结束' && this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; this.showMessage(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`); setTimeout(() => this.checkSimulationStatus(simulationId), 1000); return; } // 重置UI this.resetSimulationButtons(); } else if (data.running === true) { // 更新状态为已连接 this.showSuccess('已连接到运行中的仿真'); } } 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}`); } // 重置UI this.resetSimulationButtons(); } catch (error) { console.error('处理完成事件失败:', error); } }); // 仿真错误 this.eventSource.addEventListener('error', (event) => { try { const data = JSON.parse(event.data); this.showError(`仿真错误: ${data.message}`); // 重置UI this.resetSimulationButtons(); } catch (error) { console.error('处理错误事件失败:', error); } }); // 仿真终止 this.eventSource.addEventListener('terminated', (event) => { try { const data = JSON.parse(event.data); this.showMessage(`仿真已终止: ${data.message}`); // 在输出框中添加终止信息 const outputContent = this.shadowRoot.querySelector('#output-content'); outputContent.innerHTML += `\n\n仿真已终止:${data.message}`; // 重置UI this.resetSimulationButtons(); } catch (error) { console.error('处理终止事件失败:', error); } }); // 连接错误处理 this.eventSource.onerror = (error) => { console.error('SSE连接错误:', error); // 检查连接状态 if (this.eventSource && this.eventSource.readyState === 2) { // CLOSED // 如果还有重连次数,尝试重新连接 if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; this.showMessage(`连接断开,尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`); setTimeout(() => this.checkSimulationStatus(this.currentSimulationId), 1000); } else { this.showError('实时输出连接已断开'); this.resetSimulationButtons(); } } }; } // 检查仿真状态 async checkSimulationStatus(simulationId) { try { const response = await fetch(`/api/check-process/${simulationId}`); const data = await response.json(); if (data.running) { // 仿真仍在运行,重新连接到事件源 this.showMessage('重新连接到运行中的仿真...'); this.connectToEventSource(simulationId); } else { // 仿真已经停止,重置UI this.showMessage('仿真已结束'); this.resetSimulationButtons(); } } catch (error) { console.error('检查仿真状态失败:', error); // 假设仿真已停止 this.resetSimulationButtons(); } } async runSimulation() { const savedSelection = localStorage.getItem('xnsim-selection'); const selection = savedSelection ? JSON.parse(savedSelection) : {}; const confID = selection.configurationId; try { this.showMessage('准备运行仿真...'); const runButton = this.shadowRoot.querySelector('#run-simulation-button'); const pauseButton = this.shadowRoot.querySelector('#pause-simulation-button'); const stopButton = this.shadowRoot.querySelector('#stop-simulation-button'); runButton.disabled = true; pauseButton.disabled = false; stopButton.disabled = false; runButton.textContent = '运行中...'; // 清空并初始化输出框 const outputContent = this.shadowRoot.querySelector('#output-content'); outputContent.innerHTML = '开始执行仿真...\n'; // 准备启动参数 const simulationArgs = ['-id', confID]; // 调用后端API执行仿真 const response = await fetch('/api/run-simulation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ args: simulationArgs }) }); if (!response.ok) { const errorData = await response.json(); outputContent.innerHTML += `\n错误: ${errorData.message || '仿真执行失败'}\n`; if (errorData.output) outputContent.innerHTML += this.convertAnsiToHtml(errorData.output); if (errorData.errorOutput) outputContent.innerHTML += this.convertAnsiToHtml(errorData.errorOutput); throw new Error(errorData.message || '仿真执行失败'); } const responseData = await response.json(); // 获取进程ID const simulationId = responseData.simulationId; // 保存当前仿真ID this.currentSimulationId = simulationId; // 建立SSE连接 this.connectToEventSource(simulationId); // 根据启动结果更新UI if (responseData.success) { this.showSuccess('仿真已启动'); } else { this.showError(`仿真启动失败: ${responseData.message || '未知错误'}`); this.resetSimulationButtons(); } } catch (error) { console.error('执行仿真失败:', error); this.showError('执行仿真失败: ' + error.message); // 重置按钮状态 this.resetSimulationButtons(); // 显示错误详情 const outputContent = this.shadowRoot.querySelector('#output-content'); outputContent.innerHTML += `\n\n执行错误: ${error.message}`; } } // 添加新方法:检查并连接到已有的仿真 async checkAndConnectToExistingSimulation() { try { const response = await fetch('/api/check-xnengine'); const data = await response.json(); if (data.running) { this.showMessage('检测到正在运行的仿真,正在重新连接...'); // 更新UI以反映运行状态 const runButton = this.shadowRoot.querySelector('#run-simulation-button'); const pauseButton = this.shadowRoot.querySelector('#pause-simulation-button'); const stopButton = this.shadowRoot.querySelector('#stop-simulation-button'); if (runButton) { runButton.disabled = true; runButton.textContent = '运行中...'; } if (pauseButton) { pauseButton.disabled = false; } if (stopButton) { stopButton.disabled = false; } // 使用进程ID作为仿真ID重新连接 this.currentSimulationId = data.pid.toString(); // 清空并初始化输出框 const outputContent = this.shadowRoot.querySelector('#output-content'); outputContent.innerHTML = '重新连接到运行中的仿真...\n'; // 连接到SSE获取输出 this.connectToEventSource(this.currentSimulationId); // 更新状态为已连接 this.showSuccess('已连接到运行中的仿真'); // 重置重连尝试次数 this.reconnectAttempts = 0; } else { // 如果没有运行中的仿真,重置UI this.resetSimulationButtons(); } } catch (error) { console.error('检查XNEngine进程失败:', error); this.showError('检查仿真状态失败: ' + error.message); this.resetSimulationButtons(); } } // 添加reactivate方法,用于从缓存中恢复时检查仿真状态 async reactivate() { // 检查是否有XNEngine进程在运行 await this.checkAndConnectToExistingSimulation(); } // 在组件销毁时清理资源 disconnectedCallback() { if (this.logFilePollingInterval) { clearInterval(this.logFilePollingInterval); this.logFilePollingInterval = null; } if (this.eventSource) { this.eventSource.close(); this.eventSource = null; } this.currentSimulationId = null; this.reconnectAttempts = 0; } showError(message) { const messageElement = this.shadowRoot.querySelector('#message'); messageElement.textContent = message; messageElement.style.color = 'red'; } showMessage(message) { const messageElement = this.shadowRoot.querySelector('#message'); messageElement.textContent = message; messageElement.style.color = 'blue'; } showSuccess(message) { const messageElement = this.shadowRoot.querySelector('#message'); messageElement.textContent = message; messageElement.style.color = 'green'; } render() { this.shadowRoot.innerHTML = `
模型列表
服务列表
运行输出
等待测试运行...
`; // 添加事件监听器 const runButton = this.shadowRoot.querySelector('#run-button'); runButton.addEventListener('click', () => this.runTest()); const runSimulationButton = this.shadowRoot.querySelector('#run-simulation-button'); runSimulationButton.addEventListener('click', () => this.runSimulation()); const pauseSimulationButton = this.shadowRoot.querySelector('#pause-simulation-button'); pauseSimulationButton.addEventListener('click', () => this.pauseSimulation()); const stopSimulationButton = this.shadowRoot.querySelector('#stop-simulation-button'); stopSimulationButton.addEventListener('click', () => this.stopSimulation()); } // ANSI终端颜色转换为HTML convertAnsiToHtml(text) { if (!text) return ''; // 映射ANSI颜色代码到CSS类名 const ansiToHtmlClass = { // 前景色 (30-37) 30: 'ansi-black', 31: 'ansi-red', 32: 'ansi-green', 33: 'ansi-yellow', 34: 'ansi-blue', 35: 'ansi-magenta', 36: 'ansi-cyan', 37: 'ansi-white', // 明亮前景色 (90-97) 90: 'ansi-bright-black', 91: 'ansi-bright-red', 92: 'ansi-bright-green', 93: 'ansi-bright-yellow', 94: 'ansi-bright-blue', 95: 'ansi-bright-magenta', 96: 'ansi-bright-cyan', 97: 'ansi-bright-white', // 背景色 (40-47) 40: 'ansi-bg-black', 41: 'ansi-bg-red', 42: 'ansi-bg-green', 43: 'ansi-bg-yellow', 44: 'ansi-bg-blue', 45: 'ansi-bg-magenta', 46: 'ansi-bg-cyan', 47: 'ansi-bg-white', // 文字样式 1: 'ansi-bold', 3: 'ansi-italic', 4: 'ansi-underline' }; // 替换ANSI转义序列 const parts = []; let currentSpan = null; let currentClasses = []; // 先分割ANSI序列和文本 const regex = /\x1b\[([\d;]*)m/g; let lastIndex = 0; let match; while ((match = regex.exec(text)) !== null) { // 添加匹配前的文本 if (match.index > lastIndex) { parts.push({ text: text.substring(lastIndex, match.index), classes: [...currentClasses] }); } // 处理ANSI代码 const codes = match[1].split(';').map(Number); // 重置所有样式 if (codes.includes(0) || codes.length === 0) { currentClasses = []; } else { // 添加样式 for (const code of codes) { if (ansiToHtmlClass[code]) { currentClasses.push(ansiToHtmlClass[code]); } } } lastIndex = regex.lastIndex; } // 添加最后一段文本 if (lastIndex < text.length) { parts.push({ text: text.substring(lastIndex), classes: [...currentClasses] }); } // 生成HTML let html = ''; for (const part of parts) { if (part.text) { if (part.classes.length > 0) { html += `${this.escapeHtml(part.text)}`; } else { html += this.escapeHtml(part.text); } } } return html; } // 转义HTML特殊字符 escapeHtml(text) { return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } async pauseSimulation() { const simulationId = this.currentSimulationId; if (!simulationId) return; try { const button = this.shadowRoot.querySelector('#pause-simulation-button'); const isPaused = button.textContent === '继续仿真'; // 调用后端API暂停/继续仿真 const response = await fetch('/api/pause-simulation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: simulationId, pause: !isPaused }) }); if (!response.ok) { throw new Error('操作失败'); } // 更新按钮状态 button.textContent = isPaused ? '暂停仿真' : '继续仿真'; this.showMessage(isPaused ? '仿真已继续' : '仿真已暂停'); } catch (error) { console.error('暂停/继续仿真失败:', error); this.showError('操作失败: ' + error.message); } } async stopSimulation() { const simulationId = this.currentSimulationId; if (!simulationId) return; try { // 调用后端API停止仿真 const response = await fetch('/api/stop-simulation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: simulationId }) }); if (!response.ok) { throw new Error('停止仿真失败'); } // 关闭SSE连接 if (this.eventSource) { this.eventSource.close(); this.eventSource = null; } // 重置按钮状态 this.resetSimulationButtons(); this.currentSimulationId = null; this.showSuccess('仿真已停止'); } catch (error) { console.error('停止仿真失败:', error); this.showError('停止仿真失败: ' + error.message); } } resetSimulationButtons() { const runButton = this.shadowRoot.querySelector('#run-simulation-button'); const pauseButton = this.shadowRoot.querySelector('#pause-simulation-button'); const stopButton = this.shadowRoot.querySelector('#stop-simulation-button'); runButton.disabled = false; runButton.textContent = '运行仿真'; pauseButton.disabled = true; stopButton.disabled = true; pauseButton.textContent = '暂停仿真'; } } customElements.define('run-sim', RunSim);