diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db
index 3081d58..f435023 100644
Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ
diff --git a/XNSimHtml/components/content-area.js b/XNSimHtml/components/content-area.js
index 9cb5906..8e7aa0f 100644
--- a/XNSimHtml/components/content-area.js
+++ b/XNSimHtml/components/content-area.js
@@ -111,11 +111,8 @@ class ContentArea extends HTMLElement {
case 'service-development':
contentElement = document.createElement('service-development');
break;
- case 'run-test':
- contentElement = document.createElement('run-test');
- break;
- case 'run-simulation':
- contentElement = document.createElement('run-simulation');
+ case 'run-sim':
+ contentElement = document.createElement('run-sim');
break;
case 'simulation-monitor':
contentElement = document.createElement('simulation-monitor');
diff --git a/XNSimHtml/components/run-test.js b/XNSimHtml/components/run-sim.js
similarity index 57%
rename from XNSimHtml/components/run-test.js
rename to XNSimHtml/components/run-sim.js
index 3f2dc45..c355d33 100644
--- a/XNSimHtml/components/run-test.js
+++ b/XNSimHtml/components/run-sim.js
@@ -1,4 +1,4 @@
-class RunTest extends HTMLElement {
+class RunSim extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
@@ -7,12 +7,17 @@ class RunTest extends HTMLElement {
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() {
@@ -280,12 +285,298 @@ class RunTest extends HTMLElement {
}
}
+ // 连接到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) {
@@ -477,6 +768,47 @@ class RunTest extends HTMLElement {
background-color: #cccccc;
cursor: not-allowed;
}
+
+ .button-group {
+ display: flex;
+ gap: 10px;
+ }
+
+ .simulation-button {
+ background-color: #4caf50;
+ }
+
+ .simulation-button:hover {
+ background-color: #45a049;
+ }
+
+ .simulation-button:disabled {
+ background-color: #cccccc;
+ }
+
+ .pause-button {
+ background-color: #ff9800;
+ }
+
+ .pause-button:hover {
+ background-color: #f57c00;
+ }
+
+ .pause-button:disabled {
+ background-color: #cccccc;
+ }
+
+ .stop-button {
+ background-color: #f44336;
+ }
+
+ .stop-button:hover {
+ background-color: #d32f2f;
+ }
+
+ .stop-button:disabled {
+ background-color: #cccccc;
+ }
@@ -494,7 +826,12 @@ class RunTest extends HTMLElement {
等待测试运行...
@@ -505,6 +842,15 @@ class RunTest extends HTMLElement {
// 添加事件监听器
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
@@ -618,6 +964,87 @@ class RunTest extends HTMLElement {
.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-test', RunTest);
\ No newline at end of file
+customElements.define('run-sim', RunSim);
\ No newline at end of file
diff --git a/XNSimHtml/components/run-simulation.js b/XNSimHtml/components/run-simulation.js
deleted file mode 100644
index f5228d7..0000000
--- a/XNSimHtml/components/run-simulation.js
+++ /dev/null
@@ -1,1075 +0,0 @@
-class RunSimulation extends HTMLElement {
- constructor() {
- super();
- this.attachShadow({ mode: 'open' });
- this.scenarioFiles = [];
- this.currentScenario = null;
- this.modelGroups = [];
- this.services = [];
- this.currentSimulationId = null;
- this.eventSource = null;
- this.reconnectAttempts = 0;
- this.maxReconnectAttempts = 3;
- }
-
- // 添加reactivate方法,用于从缓存中恢复时检查仿真状态
- async reactivate() {
- // 确保先加载场景文件列表
- await this.fetchScenarioFiles();
-
- // 如果之前已经选择了配置文件,恢复配置文件下拉框的选中状态
- if (this.currentScenario) {
- const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
- if (scenarioSelect) {
- // 检查所选配置文件是否在列表中
- const fileExists = Array.from(scenarioSelect.options).some(opt => opt.value === this.currentScenario);
-
- if (fileExists) {
- scenarioSelect.value = this.currentScenario;
-
- // 恢复模型和服务列表显示
- this.updateModelAndServiceLists();
- } else {
- console.warn('之前选择的配置文件不再可用:', this.currentScenario);
- this.currentScenario = null;
- }
- }
- }
-
- // 检查是否有XNEngine进程在运行
- 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-button');
- if (runButton) {
- runButton.disabled = true;
- runButton.textContent = '运行中...';
- }
-
- // 显示终止按钮
- const stopButton = this.shadowRoot.querySelector('#stop-button');
- if (stopButton) {
- stopButton.style.display = 'block';
- }
-
- // 使用进程ID作为仿真ID重新连接
- this.currentSimulationId = data.pid.toString();
- this.connectToEventSource(this.currentSimulationId);
-
- // 清空并初始化输出框
- const outputContent = this.shadowRoot.querySelector('#output-content');
- outputContent.innerHTML = '重新连接到运行中的仿真...\n';
- } else {
- // 没有运行的XNEngine进程,重置UI
- this.resetUIAfterCompletion();
- }
- } catch (error) {
- console.error('检查XNEngine进程失败:', error);
- this.showError('检查仿真状态失败: ' + error.message);
- this.resetUIAfterCompletion();
- }
- }
-
- // 检查仿真状态
- async checkSimulationStatus(simulationId) {
- try {
- const response = await fetch(`/api/simulation-status/${simulationId}`);
- const data = await response.json();
-
- if (data.running) {
- // 仿真仍在运行,重新连接到事件源
- this.showMessage('重新连接到运行中的仿真...');
-
- // 更新UI以反映运行状态
- const runButton = this.shadowRoot.querySelector('#run-button');
- if (runButton) {
- runButton.disabled = true;
- runButton.textContent = '运行中...';
- }
-
- // 显示终止按钮
- const stopButton = this.shadowRoot.querySelector('#stop-button');
- if (stopButton) {
- stopButton.style.display = 'block';
- }
-
- // 重新连接到事件源
- this.connectToEventSource(simulationId);
- } else {
- // 仿真已经停止,重置UI
- this.showMessage('仿真已结束');
- this.resetUIAfterCompletion();
- }
- } catch (error) {
- console.error('检查仿真状态失败:', error);
- // 假设仿真已停止
- this.resetUIAfterCompletion();
- }
- }
-
- connectedCallback() {
- // 先加载场景文件列表
- this.fetchScenarioFiles();
- // 然后渲染UI
- this.render();
-
- // 最后检查是否有XNEngine进程在运行
- this.checkAndConnectToExistingSimulation();
- }
-
- 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);
- });
- }
-
- // 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 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 stopButton = this.shadowRoot.querySelector('#stop-button');
- stopButton.style.display = 'block';
-
- // 清空并初始化输出框
- const outputContent = this.shadowRoot.querySelector('#output-content');
- outputContent.innerHTML = '开始执行仿真...\n';
-
- // 关闭之前的EventSource连接
- if (this.eventSource) {
- this.eventSource.close();
- this.eventSource = null;
- }
-
- // 保存当前仿真信息
- this.currentSimulationId = Date.now().toString();
-
- // 准备启动参数
- const simulationArgs = ['-f', selectedScenario];
-
- // 调用后端API执行仿真
- const response = await fetch('/api/run-simulation', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- id: this.currentSimulationId,
- 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 simulationId = responseData.simulationId || this.currentSimulationId;
- this.currentSimulationId = simulationId;
-
- // 如果是连接到现有进程,显示相应消息
- if (responseData.isExisting) {
- this.showMessage('已连接到运行中的仿真');
- outputContent.innerHTML = '已连接到运行中的仿真...\n';
-
- // 如果有配置文件路径,自动选择
- if (responseData.scenarioFile) {
- const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
- if (scenarioSelect) {
- // 检查配置文件是否在列表中
- const fileExists = Array.from(scenarioSelect.options).some(opt => opt.value === responseData.scenarioFile);
- if (fileExists) {
- scenarioSelect.value = responseData.scenarioFile;
- this.currentScenario = responseData.scenarioFile;
- // 更新模型和服务列表
- this.updateModelAndServiceLists();
- }
- }
- }
- } else {
- this.showSuccess(`仿真已启动`);
- }
-
- // 设置SSE连接获取实时输出
- this.connectToEventSource(simulationId);
-
- } catch (error) {
- console.error('执行仿真失败:', error);
- this.showError('执行仿真失败: ' + error.message);
-
- // 显示错误详情
- const outputContent = this.shadowRoot.querySelector('#output-content');
- outputContent.innerHTML += `\n\n执行错误: ${error.message}`;
-
- // 隐藏终止按钮并重置运行按钮
- const stopButton = this.shadowRoot.querySelector('#stop-button');
- stopButton.style.display = 'none';
-
- const runButton = this.shadowRoot.querySelector('#run-button');
- runButton.disabled = false;
- runButton.textContent = '运行仿真';
-
- // 清除当前仿真ID和关闭SSE连接
- this.closeEventSource();
- this.currentSimulationId = null;
- }
- }
-
- // 连接到SSE事件源获取实时输出
- connectToEventSource(simulationId) {
- // 关闭之前的连接
- this.closeEventSource();
-
- // 创建新的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.checkAndConnectToExistingSimulation(), 1000);
- return;
- }
-
- // 重置UI
- this.resetUIAfterCompletion();
- } 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.resetUIAfterCompletion();
- } catch (error) {
- console.error('处理完成事件失败:', error);
- }
- });
-
- // 仿真错误
- this.eventSource.addEventListener('error', (event) => {
- try {
- const data = JSON.parse(event.data);
- this.showError(`仿真错误: ${data.message}`);
-
- // 重置UI
- this.resetUIAfterCompletion();
- } 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.resetUIAfterCompletion();
- } catch (error) {
- console.error('处理终止事件失败:', error);
- }
- });
-
- // 仿真超时
- this.eventSource.addEventListener('timeout', (event) => {
- try {
- const data = JSON.parse(event.data);
- this.showError(`仿真超时: ${data.message}`);
-
- // 在输出框中添加超时信息
- const outputContent = this.shadowRoot.querySelector('#output-content');
- outputContent.innerHTML += `\n\n仿真超时:${data.message}`;
-
- // 重置UI
- this.resetUIAfterCompletion();
- } 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.checkAndConnectToExistingSimulation(), 1000);
- } else {
- this.showError('实时输出连接已断开');
- this.resetUIAfterCompletion();
- }
- }
- };
- }
-
- // 关闭SSE连接
- closeEventSource() {
- if (this.eventSource) {
- // 通知后端清理tail进程
- if (this.currentSimulationId) {
- fetch(`/api/cleanup-simulation/${this.currentSimulationId}`, {
- method: 'POST'
- }).catch(error => {
- console.error('清理仿真资源失败:', error);
- });
- }
-
- this.eventSource.close();
- this.eventSource = null;
- }
- }
-
- // 在仿真完成后重置UI
- resetUIAfterCompletion() {
- // 隐藏终止按钮
- const stopButton = this.shadowRoot.querySelector('#stop-button');
- if (stopButton) {
- stopButton.style.display = 'none';
- }
-
- // 重置运行按钮
- const runButton = this.shadowRoot.querySelector('#run-button');
- if (runButton) {
- runButton.disabled = false;
- runButton.textContent = '运行仿真';
- }
-
- // 关闭SSE连接
- this.closeEventSource();
-
- // 清除当前仿真ID和重连计数
- this.currentSimulationId = null;
- this.reconnectAttempts = 0;
- }
-
- async stopSimulation() {
- if (!this.currentSimulationId) {
- this.showMessage('没有正在运行的仿真');
- return;
- }
-
- try {
- this.showMessage('正在终止仿真...');
-
- const response = await fetch('/api/stop-simulation', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- id: this.currentSimulationId
- })
- });
-
- if (response.ok) {
- const result = await response.json();
- this.showSuccess(`${result.message || '仿真已终止'}`);
-
- // 终止信息会通过SSE推送到UI
- // 此处不需要额外操作
- } else {
- const errorData = await response.json();
- this.showError(`终止仿真失败: ${errorData.message || '未知错误'}`);
-
- // 可能是SSE已断开,手动重置UI
- this.resetUIAfterCompletion();
- }
- } catch (error) {
- console.error('终止仿真失败:', error);
- this.showError('终止仿真失败: ' + error.message);
-
- // 手动重置UI
- this.resetUIAfterCompletion();
- }
- }
-
- 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 stopButton = this.shadowRoot.querySelector('#stop-button');
- stopButton.addEventListener('click', () => this.stopSimulation());
-
- const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
- scenarioSelect.addEventListener('change', (event) => this.onScenarioSelected(event));
- }
-
- // 添加新方法:检查并连接到已有的仿真
- 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-button');
- if (runButton) {
- runButton.disabled = true;
- runButton.textContent = '运行中...';
- }
-
- // 显示终止按钮
- const stopButton = this.shadowRoot.querySelector('#stop-button');
- if (stopButton) {
- stopButton.style.display = 'block';
- }
-
- // 使用进程ID作为仿真ID重新连接
- this.currentSimulationId = data.pid.toString();
-
- // 确保场景文件列表已加载
- if (this.scenarioFiles.length === 0) {
- await this.fetchScenarioFiles();
- }
-
- // 如果有配置文件路径,自动选择并加载内容
- if (data.scenarioFile) {
- const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
- if (scenarioSelect) {
- // 检查配置文件是否在列表中
- const fileExists = Array.from(scenarioSelect.options).some(opt => opt.value === data.scenarioFile);
- if (fileExists) {
- // 先设置当前场景
- this.currentScenario = data.scenarioFile;
-
- // 加载配置文件内容
- try {
- this.showMessage('加载配置文件内容...');
- const response = await fetch(`/api/file-content?path=${encodeURIComponent(data.scenarioFile)}`);
-
- if (!response.ok) {
- throw new Error('无法获取配置文件内容');
- }
-
- const xmlContent = await response.text();
- this.parseScenarioXML(xmlContent);
-
- // 更新下拉框选择
- scenarioSelect.value = data.scenarioFile;
-
- // 更新模型和服务列表
- this.updateModelAndServiceLists();
- this.showMessage('配置文件加载完成');
- } catch (error) {
- console.error('加载配置文件失败:', error);
- this.showError('加载配置文件失败: ' + error.message);
- }
- } else {
- console.warn('配置文件不在列表中:', data.scenarioFile);
- this.showError('配置文件不在列表中: ' + data.scenarioFile);
- }
- }
- }
-
- // 清空并初始化输出框
- const outputContent = this.shadowRoot.querySelector('#output-content');
- outputContent.innerHTML = '重新连接到运行中的仿真...\n';
-
- // 连接到SSE获取输出
- this.connectToEventSource(this.currentSimulationId);
-
- // 更新状态为已连接
- this.showSuccess('已连接到运行中的仿真');
-
- // 重置重连尝试次数
- this.reconnectAttempts = 0;
- } else {
- // 如果没有运行中的仿真,重置UI
- this.resetUIAfterCompletion();
- }
- } catch (error) {
- console.error('检查XNEngine进程失败:', error);
- this.showError('检查仿真状态失败: ' + error.message);
- this.resetUIAfterCompletion();
- }
- }
-
- // 添加组件销毁时的清理方法
- disconnectedCallback() {
- // 清理SSE连接和相关资源
- this.closeEventSource();
-
- // 重置所有状态
- this.currentSimulationId = null;
- this.reconnectAttempts = 0;
- this.scenarioFiles = [];
- this.currentScenario = null;
- this.modelGroups = [];
- this.services = [];
- }
-}
-
-customElements.define('run-simulation', RunSimulation);
\ No newline at end of file
diff --git a/XNSimHtml/components/sub-toolbar.js b/XNSimHtml/components/sub-toolbar.js
index c7fbbb1..b71c0ea 100644
--- a/XNSimHtml/components/sub-toolbar.js
+++ b/XNSimHtml/components/sub-toolbar.js
@@ -239,10 +239,6 @@ class SubToolbar extends HTMLElement {