class RunTest extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.scenarioFiles = []; this.currentScenario = null; this.modelGroups = []; this.services = []; this.eventSource = null; } connectedCallback() { this.fetchScenarioFiles(); this.render(); } async fetchScenarioFiles() { try { const response = await fetch('/api/scenario-files'); if (!response.ok) { throw new Error('无法获取场景文件'); } 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)}`); if (!response.ok) { 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"); // 解析模型组和模型 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') }); }); this.modelGroups.push({ name: groupName, models: models }); }); // 解析服务 this.services = []; const serviceElements = xmlDoc.querySelectorAll('ServicesList > Service'); serviceElements.forEach(serviceElem => { this.services.push({ name: serviceElem.getAttribute('Name'), className: serviceElem.getAttribute('ClassName') }); }); } catch (error) { console.error('解析XML失败:', error); throw new Error('解析XML失败: ' + error.message); } } updateModelAndServiceLists() { // 更新模型列表 const modelListContainer = this.shadowRoot.querySelector('#model-list'); 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 modelsList = document.createElement('div'); modelsList.className = 'list-items'; group.models.forEach(model => { const modelItem = document.createElement('div'); modelItem.className = 'list-item'; modelItem.textContent = model.name; modelsList.appendChild(modelItem); }); groupItem.appendChild(modelsList); modelListContainer.appendChild(groupItem); }); // 更新服务列表 const serviceListContainer = this.shadowRoot.querySelector('#service-list'); serviceListContainer.innerHTML = ''; this.services.forEach(service => { const serviceItem = document.createElement('div'); serviceItem.className = 'list-item'; serviceItem.textContent = service.name; serviceListContainer.appendChild(serviceItem); }); } async runTest() { const selectedScenario = this.shadowRoot.querySelector('#scenario-select').value; if (!selectedScenario) { this.showError('请先选择配置文件'); return; } 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'; // 关闭之前的EventSource连接 if (this.eventSource) { this.eventSource.close(); this.eventSource = null; } // 准备启动参数 const simulationArgs = ['-f', selectedScenario, '-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(); // 连接到SSE获取实时输出 this.connectToEventSource(responseData.simulationId); // 根据测试结果更新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 = '运行测试'; } } // 连接到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 || '测试已结束'); 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(); }; } // 关闭SSE连接 closeEventSource() { if (this.eventSource) { this.eventSource.close(); this.eventSource = null; } } 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 = `