diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index ae47f18..0e2cae0 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/XNSimHtml/components/data-monitor.js b/XNSimHtml/components/data-monitor.js index cdb4abd..236b996 100644 --- a/XNSimHtml/components/data-monitor.js +++ b/XNSimHtml/components/data-monitor.js @@ -53,6 +53,7 @@ class DataMonitor extends HTMLElement { this.initializeComponent(); // 重新启动定时器 this.startDataUpdateTimer(); + } async initializeComponent() { @@ -185,13 +186,17 @@ class DataMonitor extends HTMLElement { InterfaceName: item.InterfaceName, ModelStructName: item.ModelStructName, InjectValue: '', + InjectFrequency: 100, monitorData: '', - isMonitoring: false + isMonitoring: false, + isInjecting: false }); // 创建新行 const tbody = this.shadowRoot.querySelector('.data-table tbody'); const tr = document.createElement('tr'); + tr.setAttribute('data-interface', item.InterfaceName); + tr.setAttribute('data-struct', item.ModelStructName); tr.innerHTML = ` ${item.InterfaceName} ${item.ModelStructName} @@ -204,146 +209,27 @@ class DataMonitor extends HTMLElement { data-interface="${item.InterfaceName}" data-struct="${item.ModelStructName}"> + + +
- +
`; - // 为输入框绑定事件 - const input = tr.querySelector('.inject-input'); - input.addEventListener('input', (e) => { - const value = e.target.value; - const interfaceName = e.target.dataset.interface; - const modelStructName = e.target.dataset.struct; - - // 检查接口类型 - const isArray = this.isInterfaceArray(interfaceName, modelStructName); - - // 只允许数字、小数点、负号和逗号 - const validValue = value.replace(/[^0-9.,-]/g, ''); - if (value !== validValue) { - e.target.value = validValue; - } - - // 验证格式 - const isValid = this.validateInjectValue(validValue, isArray, interfaceName, modelStructName); - e.target.classList.toggle('invalid', !isValid); - - // 实时更新表格数据 - const row = this.tableData.find(r => - r.InterfaceName === interfaceName && - r.ModelStructName === modelStructName - ); - if (row) { - row.InjectValue = validValue; - } - }); - - input.addEventListener('change', (e) => { - const interfaceName = e.target.dataset.interface; - const modelStructName = e.target.dataset.struct; - const value = e.target.value; - - // 检查接口类型 - const isArray = this.isInterfaceArray(interfaceName, modelStructName); - - // 验证格式 - if (!this.validateInjectValue(value, isArray, interfaceName, modelStructName)) { - e.target.value = ''; - // 清空表格数据中的注入值 - const row = this.tableData.find(r => - r.InterfaceName === interfaceName && - r.ModelStructName === modelStructName - ); - if (row) { - row.InjectValue = ''; - } - return; - } - - // 更新表格数据 - const row = this.tableData.find(r => - r.InterfaceName === interfaceName && - r.ModelStructName === modelStructName - ); - if (row) { - row.InjectValue = value; - } - }); - - // 为按钮绑定事件 - const deleteButton = tr.querySelector('.action-button.delete'); - deleteButton.addEventListener('click', () => { - this.handleDelete(item.InterfaceName, item.ModelStructName); - }); - - const plotButton = tr.querySelector('.action-button.plot'); - plotButton.addEventListener('click', () => { - this.handlePlot(item.InterfaceName, item.ModelStructName); - }); - - const injectButton = tr.querySelector('.action-button.inject-once'); - injectButton.addEventListener('click', async () => { - // 检查监控状态 - const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); - if (!statusIndicator || !statusIndicator.classList.contains('active')) { - return; - } - - const row = this.tableData.find(r => - r.InterfaceName === item.InterfaceName && - r.ModelStructName === item.ModelStructName - ); - - if (!row) { - return; - } - - if (!row.InjectValue || row.InjectValue.trim() === '') { - alert('请先输入注入值'); - return; - } - - // 检查接口类型和注入值格式 - const isArray = this.isInterfaceArray(item.InterfaceName, item.ModelStructName); - if (!this.validateInjectValue(row.InjectValue, isArray, item.InterfaceName, item.ModelStructName)) { - alert(isArray ? '请输入正确格式的数组数据(用逗号分隔的数字)' : '请输入单个数字'); - return; - } - - try { - // 构造接口数据 - const interfaceData = { - [item.InterfaceName]: row.InjectValue - }; - - // 调用注入接口 - const response = await fetch('/api/data-monitor/inject', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - structName: item.ModelStructName, - interfaceNameAndData: JSON.stringify(interfaceData) - }) - }); - - const result = await response.json(); - if (!result.success) { - throw new Error(result.message); - } - } catch (error) { - console.error('注入失败:', error); - alert(`注入失败: ${error.message}`); - } - }); - tbody.appendChild(tr); } } @@ -1069,6 +955,33 @@ class DataMonitor extends HTMLElement { box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2); } + .frequency-input { + width: 100%; + padding: 4px 8px; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 14px; + box-sizing: border-box; + } + + .frequency-input:focus { + border-color: #1890ff; + outline: none; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + } + + .frequency-input:disabled { + background-color: #f5f5f5; + cursor: not-allowed; + } + + .action-button:disabled { + background-color: #f5f5f5; + color: #d9d9d9; + border-color: #d9d9d9; + cursor: not-allowed; + } + .floating-chart-window { position: fixed; width: 400px; @@ -1140,6 +1053,22 @@ class DataMonitor extends HTMLElement { width: 100% !important; height: 100% !important; } + + .action-button.active { + background-color: #ff4d4f; + color: white; + border-color: #ff4d4f; + } + + .action-button.active:hover { + background-color: #ff7875; + border-color: #ff7875; + } + + .inject-input:disabled { + background-color: #f5f5f5; + cursor: not-allowed; + }
@@ -1184,13 +1113,14 @@ class DataMonitor extends HTMLElement { 结构体名
数据
注入值
+ 注入频率(Hz)
操作 ${this.tableData.map(row => { return ` - + ${row.InterfaceName} ${row.ModelStructName} ${this.formatMonitorData(row.monitorData)} @@ -1200,14 +1130,27 @@ class DataMonitor extends HTMLElement { value="${row.InjectValue || ''}" placeholder="输入注入值" data-interface="${row.InterfaceName}" - data-struct="${row.ModelStructName}"> + data-struct="${row.ModelStructName}" + ${row.isInjecting ? 'disabled' : ''}> + + +
- - - + + +
@@ -1336,6 +1279,31 @@ class DataMonitor extends HTMLElement { }); }); + // 添加频率输入框的事件处理 + const frequencyInputs = this.shadowRoot.querySelectorAll('.frequency-input'); + frequencyInputs.forEach(input => { + input.addEventListener('change', (e) => { + const interfaceName = e.target.dataset.interface; + const modelStructName = e.target.dataset.struct; + const value = parseInt(e.target.value); + + // 验证频率范围 + if (value < 0 || value > 1000) { + e.target.value = 100; // 默认值 + return; + } + + // 更新表格数据 + const row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + if (row) { + row.InjectFrequency = value; + } + }); + }); + // 添加注入一次按钮的事件委托 table.addEventListener('click', async (e) => { const injectButton = e.target.closest('.action-button.inject-once'); @@ -1420,6 +1388,134 @@ class DataMonitor extends HTMLElement { this.handlePlot(interfaceName, modelStructName); } }); + + // 添加连续注入按钮的事件委托 + table.addEventListener('click', async (e) => { + const continuousInjectButton = e.target.closest('.action-button.inject-continuous'); + if (continuousInjectButton) { + // 检查监控状态 + const statusIndicator = this.shadowRoot.getElementById('statusIndicator'); + if (!statusIndicator || !statusIndicator.classList.contains('active')) { + return; + } + + const interfaceName = continuousInjectButton.getAttribute('data-interface'); + const modelStructName = continuousInjectButton.getAttribute('data-struct'); + + if (!interfaceName || !modelStructName) { + console.error('按钮缺少必要的数据属性'); + return; + } + + const row = this.tableData.find(r => + r.InterfaceName === interfaceName && + r.ModelStructName === modelStructName + ); + + if (!row) { + console.error('未找到对应的行数据:', { interfaceName, modelStructName }); + return; + } + + // 如果正在连续注入,则停止注入 + if (continuousInjectButton.classList.contains('active')) { + try { + // 调用停止注入接口 + const response = await fetch('/api/data-monitor/stop-continuous', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + structName: modelStructName, + interfaceName: interfaceName + }) + }); + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message); + } + + // 更新按钮和输入框状态 + continuousInjectButton.textContent = '连续注入'; + continuousInjectButton.classList.remove('active'); + const tr = continuousInjectButton.closest('tr'); + const input = tr.querySelector('.inject-input'); + const injectButton = tr.querySelector('.action-button.inject-once'); + const frequencyInput = tr.querySelector('.frequency-input'); + const deleteButton = tr.querySelector('.action-button.delete'); + input.disabled = false; + injectButton.disabled = false; + frequencyInput.disabled = false; + deleteButton.disabled = false; + + // 更新表格数据状态 + row.isInjecting = false; + } catch (error) { + console.error('停止注入失败:', error); + alert(`停止注入失败: ${error.message}`); + } + return; + } + + if (!row.InjectValue || row.InjectValue.trim() === '') { + alert('请先输入注入值'); + return; + } + + // 检查接口类型和注入值格式 + const isArray = this.isInterfaceArray(interfaceName, modelStructName); + if (!this.validateInjectValue(row.InjectValue, isArray, interfaceName, modelStructName)) { + alert(isArray ? '请输入正确格式的数组数据(用逗号分隔的数字)' : '请输入单个数字'); + return; + } + + try { + // 构造接口数据 + const interfaceData = { + [interfaceName]: row.InjectValue + }; + + // 调用连续注入接口 + const response = await fetch('/api/data-monitor/start-continuous', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + structName: modelStructName, + interfaceNameAndData: JSON.stringify(interfaceData), + frequency: row.InjectFrequency || 100 + }) + }); + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message); + } + + // 更新按钮和输入框状态 + continuousInjectButton.textContent = '停止注入'; + continuousInjectButton.classList.add('active'); + const tr = continuousInjectButton.closest('tr'); + const input = tr.querySelector('.inject-input'); + const injectButton = tr.querySelector('.action-button.inject-once'); + const frequencyInput = tr.querySelector('.frequency-input'); + const deleteButton = tr.querySelector('.action-button.delete'); + input.disabled = true; + injectButton.disabled = true; + frequencyInput.disabled = true; + deleteButton.disabled = true; + + // 更新表格数据状态 + row.isInjecting = true; + } catch (error) { + console.error('连续注入失败:', error); + alert(`连续注入失败: ${error.message}`); + } + } + }); } /** diff --git a/XNSimHtml/routes/DataMonitor.js b/XNSimHtml/routes/DataMonitor.js index c2b6b68..b663256 100644 --- a/XNSimHtml/routes/DataMonitor.js +++ b/XNSimHtml/routes/DataMonitor.js @@ -2,6 +2,9 @@ const express = require('express'); const router = express.Router(); const systemMonitor = require('../utils/xnCoreService'); +// 存储连续注入状态 +const continuousInjectStatus = new Map(); + /** * @brief 启动数据监控 * @route POST /api/data-monitor/start @@ -141,11 +144,66 @@ router.post('/start-continuous', async (req, res) => { }); } - const result = systemMonitor.startInjectContinuous(structName, interfaceNameAndData, frequency); - if (result.includes('失败')) { - return res.status(500).json({ success: false, message: result }); + // 检查是否已存在该结构体的注入状态 + if (continuousInjectStatus.has(structName)) { + const status = continuousInjectStatus.get(structName); + + // 检查频率是否相同 + if (status.frequency !== frequency) { + return res.status(400).json({ + success: false, + message: `该结构体已有其他接口以 ${status.frequency}ms 的频率进行注入,请使用相同的频率` + }); + } + + // 合并接口数据 + try { + const existingData = JSON.parse(status.interfaceNameAndData); + const newData = JSON.parse(interfaceNameAndData); + const mergedData = { ...existingData, ...newData }; + status.interfaceNameAndData = JSON.stringify(mergedData); + } catch (error) { + return res.status(400).json({ + success: false, + message: '接口数据格式错误' + }); + } + + // 启动注入 + const result = systemMonitor.startInjectContinuous(structName, status.interfaceNameAndData, frequency); + if (result.includes('失败')) { + continuousInjectStatus.delete(structName); + return res.status(500).json({ success: false, message: result }); + } + } else { + // 创建新的注入状态 + continuousInjectStatus.set(structName, { + interfaceNameAndData, + frequency, + isInjecting: false + }); + + // 启动注入 + const result = systemMonitor.startInjectContinuous(structName, interfaceNameAndData, frequency); + if (result.includes('失败')) { + continuousInjectStatus.delete(structName); + return res.status(500).json({ success: false, message: result }); + } + + // 更新注入状态 + const status = continuousInjectStatus.get(structName); + status.isInjecting = true; } - res.json({ success: true, message: result }); + + res.json({ + success: true, + message: '启动持续注入成功', + data: { + structName, + interfaceNameAndData: continuousInjectStatus.get(structName).interfaceNameAndData, + frequency + } + }); } catch (error) { res.status(500).json({ success: false, message: `启动持续注入数据失败: ${error.message}` }); } @@ -155,20 +213,69 @@ router.post('/start-continuous', async (req, res) => { * @brief 停止持续注入数据 * @route POST /api/data-monitor/stop-continuous * @param {string} structName - 结构体名称 + * @param {string} interfaceName - 要停止的接口名称 * @returns {Object} 返回停止结果 */ router.post('/stop-continuous', async (req, res) => { try { - const { structName } = req.body; - if (!structName) { - return res.status(400).json({ success: false, message: '结构体名称不能为空' }); + const { structName, interfaceName } = req.body; + if (!structName || !interfaceName) { + return res.status(400).json({ + success: false, + message: '结构体名称和接口名称不能为空' + }); } - const result = systemMonitor.stopInjectContinuous(structName); - if (result.includes('失败')) { - return res.status(500).json({ success: false, message: result }); + // 检查是否存在该结构体的注入状态 + if (!continuousInjectStatus.has(structName)) { + return res.status(400).json({ + success: false, + message: '该结构体没有正在进行的注入' + }); + } + + const status = continuousInjectStatus.get(structName); + + try { + // 从接口数据中移除指定的接口 + const existingData = JSON.parse(status.interfaceNameAndData); + delete existingData[interfaceName]; + + // 如果还有其他接口在注入,更新状态 + if (Object.keys(existingData).length > 0) { + status.interfaceNameAndData = JSON.stringify(existingData); + const result = systemMonitor.startInjectContinuous(structName, status.interfaceNameAndData, status.frequency); + if (result.includes('失败')) { + return res.status(500).json({ success: false, message: result }); + } + res.json({ + success: true, + message: '停止指定接口的注入成功', + data: { + structName, + remainingInterfaces: Object.keys(existingData) + } + }); + } else { + // 如果没有其他接口在注入,停止整个结构体的注入 + const result = systemMonitor.stopInjectContinuous(structName); + if (result.includes('失败')) { + return res.status(500).json({ success: false, message: result }); + } + + // 删除注入状态 + continuousInjectStatus.delete(structName); + res.json({ + success: true, + message: '停止所有接口的注入成功' + }); + } + } catch (error) { + return res.status(400).json({ + success: false, + message: '接口数据格式错误' + }); } - res.json({ success: true, message: result }); } catch (error) { res.status(500).json({ success: false, message: `停止持续注入数据失败: ${error.message}` }); } diff --git a/XNSimHtml/utils/xnCoreService.js b/XNSimHtml/utils/xnCoreService.js index 305c92e..f793b6e 100644 --- a/XNSimHtml/utils/xnCoreService.js +++ b/XNSimHtml/utils/xnCoreService.js @@ -68,13 +68,26 @@ try { // 注册进程退出时的清理函数 function performCleanup() { console.log('正在执行清理操作...'); - if (loginLib) { - try { + try { + // 清理 loginLib + if (loginLib) { loginLib.cleanup(); - console.log('清理操作完成'); - } catch (error) { - console.error('清理操作失败:', error); } + + // 清理 monitorLib + if (monitorLib) { + // 停止所有监控 + stopMonitorSystemInfo(); + stopMonitorModelInfo(); + // 停止所有数据监控和注入 + stopDataMonitor(''); + stopInjectContinuous(''); + // 清理监控服务器资源 + monitorLib.XN_Cleanup(); + } + console.log('清理操作完成'); + } catch (error) { + console.error('清理操作失败:', error); } }