1050 lines
38 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = `
<style>
:host {
display: block;
height: 100%;
overflow: auto;
padding: 16px;
box-sizing: border-box;
}
.test-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.content-container {
display: flex;
flex: 1;
gap: 20px;
overflow: hidden;
}
.lists-container {
width: 320px;
display: flex;
flex-direction: column;
gap: 15px;
overflow: auto;
}
.list-section {
border: 1px solid #e0e0e0;
border-radius: 4px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.list-header {
background-color: #f5f5f5;
padding: 10px;
font-weight: bold;
border-bottom: 1px solid #e0e0e0;
}
.list-content {
overflow: auto;
padding: 10px;
max-height: 200px;
}
.list-group {
margin-bottom: 15px;
}
.list-group-header {
font-weight: bold;
color: #333;
margin-bottom: 2px;
}
.list-group-info {
font-size: 0.85em;
color: #666;
margin-bottom: 8px;
padding-left: 2px;
}
.list-items {
padding-left: 15px;
}
.list-item {
padding: 5px 0;
}
.output-container {
flex: 1;
display: flex;
flex-direction: column;
border: 1px solid #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.output-header {
background-color: #f5f5f5;
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 {
flex: 1;
background-color: #2b2b2b;
color: #e0e0e0;
font-family: monospace;
padding: 10px;
overflow: auto;
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; }
.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; }
.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; }
.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;
}
button:disabled {
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;
}
</style>
<div class="test-container">
<div class="content-container">
<div class="lists-container">
<div class="list-section">
<div class="list-header">模型列表</div>
<div id="model-list" class="list-content"></div>
</div>
<div class="list-section">
<div class="list-header">服务列表</div>
<div id="service-list" class="list-content"></div>
</div>
</div>
<div class="output-container">
<div class="output-header">
<span>运行输出</span>
<div id="message"></div>
<div class="button-group">
<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 id="output-content" class="output-content">等待测试运行...</div>
</div>
</div>
</div>
`;
// 添加事件监听器
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 += `<span class="${part.classes.join(' ')}">${this.escapeHtml(part.text)}</span>`;
} else {
html += this.escapeHtml(part.text);
}
}
}
return html;
}
// 转义HTML特殊字符
escapeHtml(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.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-sim', RunSim);