V0.21.11.250611_alpha:数据监控连续注入功能已可用

This commit is contained in:
jinchao 2025-06-11 15:25:25 +08:00
parent 1e2581a239
commit 2fe987f44a
4 changed files with 369 additions and 153 deletions

Binary file not shown.

View File

@ -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 = `
<td title="${item.InterfaceName}">${item.InterfaceName}</td>
<td title="${item.ModelStructName}">${item.ModelStructName}</td>
@ -204,146 +209,27 @@ class DataMonitor extends HTMLElement {
data-interface="${item.InterfaceName}"
data-struct="${item.ModelStructName}">
</td>
<td>
<input type="number"
class="frequency-input"
value="100"
min="10"
max="10000"
step="10"
placeholder="输入频率"
data-interface="${item.InterfaceName}"
data-struct="${item.ModelStructName}">
</td>
<td>
<div class="action-buttons">
<button class="action-button plot" data-interface="${item.InterfaceName}" data-struct="${item.ModelStructName}">绘图</button>
<button class="action-button inject-once" data-interface="${item.InterfaceName}" data-struct="${item.ModelStructName}">注入一次</button>
<button class="action-button inject-continuous" data-interface="${item.ModelStructName}" data-struct="${item.ModelStructName}">连续注入</button>
<button class="action-button inject-continuous" data-interface="${item.InterfaceName}" data-struct="${item.ModelStructName}">连续注入</button>
<button class="action-button delete" data-interface="${item.InterfaceName}" data-struct="${item.ModelStructName}">删除</button>
</div>
</td>
`;
// 为输入框绑定事件
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;
}
</style>
<div class="toolbar">
<div class="toolbar-left">
@ -1184,13 +1113,14 @@ class DataMonitor extends HTMLElement {
<th style="width: 200px">结构体名<div class="resizer"></div></th>
<th style="width: 200px">数据<div class="resizer"></div></th>
<th style="width: 200px">注入值<div class="resizer"></div></th>
<th style="width: 120px">注入频率(Hz)<div class="resizer"></div></th>
<th>操作</th>
</tr>
</thead>
<tbody>
${this.tableData.map(row => {
return `
<tr>
<tr data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}">
<td title="${row.InterfaceName}">${row.InterfaceName}</td>
<td title="${row.ModelStructName}">${row.ModelStructName}</td>
<td class="data-cell" title="${this.formatMonitorData(row.monitorData)}">${this.formatMonitorData(row.monitorData)}</td>
@ -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' : ''}>
</td>
<td>
<input type="number"
class="frequency-input"
value="${row.InjectFrequency || 100}"
min="10"
max="10000"
step="10"
placeholder="输入频率"
data-interface="${row.InterfaceName}"
data-struct="${row.ModelStructName}"
${row.isInjecting ? 'disabled' : ''}>
</td>
<td>
<div class="action-buttons">
<button class="action-button plot" data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}">绘图</button>
<button class="action-button inject-once" data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}">注入一次</button>
<button class="action-button inject-continuous" data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}">连续注入</button>
<button class="action-button delete" data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}">删除</button>
<button class="action-button inject-once" data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}" ${row.isInjecting ? 'disabled' : ''}>注入一次</button>
<button class="action-button inject-continuous ${row.isInjecting ? 'active' : ''}" data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}">${row.isInjecting ? '停止注入' : '连续注入'}</button>
<button class="action-button delete" data-interface="${row.InterfaceName}" data-struct="${row.ModelStructName}" ${row.isInjecting ? 'disabled' : ''}>删除</button>
</div>
</td>
</tr>
@ -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}`);
}
}
});
}
/**

View File

@ -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}` });
}

View File

@ -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);
}
}