将运行测试和运行仿真合二为一

This commit is contained in:
jinchao 2025-05-29 10:01:36 +08:00
parent 5ae0ecae52
commit 0296daa268
6 changed files with 434 additions and 1099 deletions

Binary file not shown.

View File

@ -111,11 +111,8 @@ class ContentArea extends HTMLElement {
case 'service-development': case 'service-development':
contentElement = document.createElement('service-development'); contentElement = document.createElement('service-development');
break; break;
case 'run-test': case 'run-sim':
contentElement = document.createElement('run-test'); contentElement = document.createElement('run-sim');
break;
case 'run-simulation':
contentElement = document.createElement('run-simulation');
break; break;
case 'simulation-monitor': case 'simulation-monitor':
contentElement = document.createElement('simulation-monitor'); contentElement = document.createElement('simulation-monitor');

View File

@ -1,4 +1,4 @@
class RunTest extends HTMLElement { class RunSim extends HTMLElement {
constructor() { constructor() {
super(); super();
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
@ -7,12 +7,17 @@ class RunTest extends HTMLElement {
this.eventSource = null; this.eventSource = null;
this.logFileWatcher = null; this.logFileWatcher = null;
this.logFilePollingInterval = null; this.logFilePollingInterval = null;
this.currentSimulationId = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 3;
} }
connectedCallback() { connectedCallback() {
this.render(); this.render();
this.loadModelGroups(); this.loadModelGroups();
this.loadServices(); this.loadServices();
// 检查并连接到已有的仿真
this.checkAndConnectToExistingSimulation();
} }
async loadModelGroups() { 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() { disconnectedCallback() {
if (this.logFilePollingInterval) { if (this.logFilePollingInterval) {
clearInterval(this.logFilePollingInterval); clearInterval(this.logFilePollingInterval);
this.logFilePollingInterval = null; this.logFilePollingInterval = null;
} }
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
this.currentSimulationId = null;
this.reconnectAttempts = 0;
} }
showError(message) { showError(message) {
@ -477,6 +768,47 @@ class RunTest extends HTMLElement {
background-color: #cccccc; background-color: #cccccc;
cursor: not-allowed; 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;
}
</style> </style>
<div class="test-container"> <div class="test-container">
<div class="content-container"> <div class="content-container">
@ -494,7 +826,12 @@ class RunTest extends HTMLElement {
<div class="output-header"> <div class="output-header">
<span>运行输出</span> <span>运行输出</span>
<div id="message"></div> <div id="message"></div>
<div class="button-group">
<button id="run-button">运行测试</button> <button id="run-button">运行测试</button>
<button id="run-simulation-button" class="simulation-button">运行仿真</button>
<button id="pause-simulation-button" class="pause-button" disabled>暂停仿真</button>
<button id="stop-simulation-button" class="stop-button" disabled>结束仿真</button>
</div>
</div> </div>
<div id="output-content" class="output-content">等待测试运行...</div> <div id="output-content" class="output-content">等待测试运行...</div>
</div> </div>
@ -505,6 +842,15 @@ class RunTest extends HTMLElement {
// 添加事件监听器 // 添加事件监听器
const runButton = this.shadowRoot.querySelector('#run-button'); const runButton = this.shadowRoot.querySelector('#run-button');
runButton.addEventListener('click', () => this.runTest()); 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 // ANSI终端颜色转换为HTML
@ -618,6 +964,87 @@ class RunTest extends HTMLElement {
.replace(/"/g, '&quot;') .replace(/"/g, '&quot;')
.replace(/'/g, '&#039;'); .replace(/'/g, '&#039;');
} }
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); customElements.define('run-sim', RunSim);

File diff suppressed because it is too large Load Diff

View File

@ -239,10 +239,6 @@ class SubToolbar extends HTMLElement {
</div> </div>
<!-- 运行子菜单 --> <!-- 运行子菜单 -->
<div class="sub-menu" data-parent="run"> <div class="sub-menu" data-parent="run">
<div class="sub-item" data-icon="flask">
<img src="assets/icons/png/flask.png" alt="运行测试" class="icon">
运行测试
</div>
<div class="sub-item" data-icon="rocket"> <div class="sub-item" data-icon="rocket">
<img src="assets/icons/png/rocket.png" alt="运行仿真" class="icon"> <img src="assets/icons/png/rocket.png" alt="运行仿真" class="icon">
运行仿真 运行仿真
@ -345,7 +341,6 @@ class SubToolbar extends HTMLElement {
const toolIcons = { const toolIcons = {
'home': { icon: 'home', text: '主页' }, 'home': { icon: 'home', text: '主页' },
'develop': { icon: 'develop', text: '开发' }, 'develop': { icon: 'develop', text: '开发' },
'config': { icon: 'sliders', text: '配置' },
'run': { icon: 'play', text: '运行' }, 'run': { icon: 'play', text: '运行' },
'monitor': { icon: 'chart', text: '监控' }, 'monitor': { icon: 'chart', text: '监控' },
'system': { icon: 'cogs', text: '管理' } 'system': { icon: 'cogs', text: '管理' }

View File

@ -21,8 +21,7 @@
<script src="components/system-log.js"></script> <script src="components/system-log.js"></script>
<script src="components/configuration-config.js"></script> <script src="components/configuration-config.js"></script>
<script src="components/interface-config.js" type="module"></script> <script src="components/interface-config.js" type="module"></script>
<script src="components/run-test.js"></script> <script src="components/run-sim.js"></script>
<script src="components/run-simulation.js"></script>
<script src="components/simulation-monitor.js"></script> <script src="components/simulation-monitor.js"></script>
<script src="components/model-monitor.js"></script> <script src="components/model-monitor.js"></script>
<script src="components/data-monitor.js"></script> <script src="components/data-monitor.js"></script>
@ -551,15 +550,8 @@
} }
// 处理运行测试标签页 // 处理运行测试标签页
if (title === '运行测试') {
const id = 'run-test';
tabsContainer.createTab(id, title, icon, parentText, parentTool);
return;
}
// 处理运行仿真标签页
if (title === '运行仿真') { if (title === '运行仿真') {
const id = 'run-simulation'; const id = 'run-sim';
tabsContainer.createTab(id, title, icon, parentText, parentTool); tabsContainer.createTab(id, title, icon, parentText, parentTool);
return; return;
} }
@ -622,7 +614,6 @@
'Q&A': 'question', 'Q&A': 'question',
'构型配置': 'chip', '构型配置': 'chip',
'接口配置': 'plug', '接口配置': 'plug',
'运行测试': 'flask',
'运行仿真': 'rocket', '运行仿真': 'rocket',
'仿真监控': 'desktop', '仿真监控': 'desktop',
'模型监控': 'cubes', '模型监控': 'cubes',