V0.21.16.250612_alpha:数据监控的CSV文件注入基本可用

This commit is contained in:
jinchao 2025-06-12 16:42:43 +08:00
parent fc46cee50f
commit cdb3c18361
7 changed files with 316 additions and 131 deletions

5
.gitignore vendored
View File

@ -34,8 +34,3 @@
# CMakeBuild
build/
#log
log/
logs/
Packages/

Binary file not shown.

View File

@ -122,14 +122,16 @@ void CSVDataInjectThread::threadFunc()
{
// 读取第一行数据
updateData();
auto startTime = std::chrono::steady_clock::now();
while (m_running) {
int64_t nextTime = m_nextExecuteTime;
// 等待直到到达执行时间
auto now = std::chrono::system_clock::now();
auto targetTime = std::chrono::system_clock::from_time_t(nextTime / 1000)
+ std::chrono::milliseconds(nextTime % 1000);
auto now = std::chrono::steady_clock::now();
auto elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count();
auto targetTime = startTime + std::chrono::milliseconds(nextTime);
if (now < targetTime) {
std::this_thread::sleep_until(targetTime);

View File

@ -647,6 +647,7 @@ int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *structName,
}
return -1;
}
std::cout << "CSV注入线程已启动" << std::endl;
return 0;
}
@ -662,6 +663,7 @@ int XN_StopCsvDataInject(char *infoMsg, int infoMsgSize)
g_csvDataInjectThread->stop();
g_csvDataInjectThread.reset(); // 释放智能指针
std::cout << "CSV注入线程已停止" << std::endl;
return 0;
}

View File

@ -220,6 +220,14 @@ extern "C"
const int csvFilePathLen,
char *infoMsg, int infoMsgSize);
/**
* @brief csv数据注入状态
* @param infoMsg
* @param infoMsgSize
* @return 0: , -1:
*/
int XNMONITORSERVER_EXPORT XN_GetCsvDataInjectStatus(char *infoMsg, int infoMsgSize);
/**
* @brief csv数据注入
* @param infoMsg

View File

@ -17,6 +17,13 @@ class DataMonitor extends HTMLElement {
this.dataUpdateTimer = null; // 数据更新定时器
this.isActive = false; // 组件是否激活
this.chartWindows = new Map(); // 存储打开的图表窗口
this.csvState = { // CSV 相关状态
isInjecting: false,
fileName: '',
structNames: [],
filePath: '' // 添加文件路径
};
this.monitorStatus = 0; // 监控状态0-未监控1-监控中2-错误
// 绑定方法
this.handleSearch = this.handleSearch.bind(this);
@ -66,25 +73,43 @@ class DataMonitor extends HTMLElement {
});
// 初始状态设置为未监控
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const statusText = this.shadowRoot.getElementById('statusText');
if (statusIndicator) {
statusIndicator.classList.remove('active', 'error');
statusIndicator.classList.add('inactive');
}
if (statusText) {
statusText.textContent = '未监控';
}
this.monitorStatus = 0;
this.updateMonitorStatus();
} catch (error) {
console.error('初始化组件失败:', error);
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const statusText = this.shadowRoot.getElementById('statusText');
if (statusIndicator) {
statusIndicator.classList.remove('active', 'inactive');
statusIndicator.classList.add('error');
this.monitorStatus = 2;
this.updateMonitorStatus();
}
}
updateMonitorStatus() {
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const statusText = this.shadowRoot.getElementById('statusText');
if (statusIndicator) {
statusIndicator.classList.remove('active', 'inactive', 'error');
switch (this.monitorStatus) {
case 1:
statusIndicator.classList.add('active');
break;
case 2:
statusIndicator.classList.add('error');
break;
default:
statusIndicator.classList.add('inactive');
}
if (statusText) {
statusText.textContent = '监控错误';
}
if (statusText) {
switch (this.monitorStatus) {
case 1:
statusText.textContent = '监控中';
break;
case 2:
statusText.textContent = '监控错误';
break;
default:
statusText.textContent = '未监控';
}
}
}
@ -270,6 +295,14 @@ class DataMonitor extends HTMLElement {
`;
tbody.appendChild(tr);
// 如果CSV正在注入禁用除绘图外的按钮
if (this.csvState.isInjecting) {
const actionButtons = tr.querySelectorAll('.action-button:not(.plot)');
actionButtons.forEach(button => {
button.disabled = true;
});
}
}
}
@ -386,18 +419,9 @@ class DataMonitor extends HTMLElement {
}
const statusData = await statusResponse.json();
// 更新状态显示
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const statusText = this.shadowRoot.getElementById('statusText');
if (!statusData.isInitialized) {
if (statusIndicator) {
statusIndicator.classList.remove('active');
statusIndicator.classList.add('inactive');
}
if (statusText) {
statusText.textContent = '未监控';
}
this.monitorStatus = 0;
this.updateMonitorStatus();
// 更新表格中所有数据的监控状态
this.tableData.forEach(row => {
row.isMonitoring = false;
@ -413,13 +437,8 @@ class DataMonitor extends HTMLElement {
}
// DDS已初始化更新状态显示
if (statusIndicator) {
statusIndicator.classList.remove('inactive', 'error');
statusIndicator.classList.add('active');
}
if (statusText) {
statusText.textContent = '监控中';
}
this.monitorStatus = 1;
this.updateMonitorStatus();
const groupedInterfaces = this.getGroupedInterfaces();
const allData = {}; // 存储所有结构体的数据
@ -475,16 +494,8 @@ class DataMonitor extends HTMLElement {
} catch (error) {
console.error('数据更新失败:', error);
this.stopDataUpdateTimer();
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const statusText = this.shadowRoot.getElementById('statusText');
if (statusIndicator) {
statusIndicator.classList.remove('active');
statusIndicator.classList.add('error');
}
if (statusText) {
statusText.textContent = '监控错误';
}
this.monitorStatus = 2;
this.updateMonitorStatus();
this.tableData.forEach(row => {
row.isMonitoring = false;
@ -551,8 +562,7 @@ class DataMonitor extends HTMLElement {
*/
async handlePlot(interfaceName, modelStructName) {
// 检查监控状态
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
if (!statusIndicator || !statusIndicator.classList.contains('active')) {
if (this.monitorStatus !== 1) {
return; // 如果不在监控状态,直接返回
}
@ -714,10 +724,6 @@ class DataMonitor extends HTMLElement {
}
render() {
// 获取当前监控状态
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const isMonitoring = statusIndicator && statusIndicator.classList.contains('active');
// 按ModelStructName分组
const groupedInterfaces = this.filteredInterfaces.reduce((groups, item) => {
const group = groups[item.ModelStructName] || [];
@ -1415,8 +1421,6 @@ class DataMonitor extends HTMLElement {
</div>
`;
const injectInputs = this.shadowRoot.querySelectorAll('.inject-input');
// 搜索框事件
const searchInput = this.shadowRoot.querySelector('.search-input');
if (searchInput) {
@ -1466,69 +1470,71 @@ class DataMonitor extends HTMLElement {
});
});
// 添加注入值输入框的事件处理
injectInputs.forEach(input => {
// 使用事件委托处理输入框事件
table.addEventListener('input', (e) => {
const input = e.target.closest('.inject-input');
if (!input) return;
const value = input.value;
const interfaceName = input.dataset.interface;
const modelStructName = input.dataset.struct;
// 添加输入验证
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 isArray = this.isInterfaceArray(interfaceName, modelStructName);
// 只允许数字、小数点、负号和逗号
const validValue = value.replace(/[^0-9.,-]/g, '');
if (value !== validValue) {
input.value = validValue;
}
// 验证格式
const isValid = this.validateInjectValue(validValue, isArray, interfaceName, modelStructName);
input.classList.toggle('invalid', !isValid);
// 实时更新表格数据
// 实时更新表格数据
const row = this.tableData.find(r =>
r.InterfaceName === interfaceName &&
r.ModelStructName === modelStructName
);
if (row) {
row.InjectValue = validValue;
}
});
table.addEventListener('change', (e) => {
const input = e.target.closest('.inject-input');
if (!input) return;
const interfaceName = input.dataset.interface;
const modelStructName = input.dataset.struct;
const value = input.value;
// 检查接口类型
const isArray = this.isInterfaceArray(interfaceName, modelStructName);
// 验证格式
if (!this.validateInjectValue(value, isArray, interfaceName, modelStructName)) {
input.value = '';
// 清空表格数据中的注入值
const row = this.tableData.find(r =>
r.InterfaceName === interfaceName &&
r.ModelStructName === modelStructName
);
if (row) {
row.InjectValue = validValue;
row.InjectValue = '';
}
});
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;
}
});
return;
}
// 更新表格数据
const row = this.tableData.find(r =>
r.InterfaceName === interfaceName &&
r.ModelStructName === modelStructName
);
if (row) {
row.InjectValue = value;
}
});
// 添加频率输入框的事件处理
@ -1561,8 +1567,7 @@ class DataMonitor extends HTMLElement {
const injectButton = e.target.closest('.action-button.inject-once');
if (injectButton) {
// 检查监控状态
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
if (!statusIndicator || !statusIndicator.classList.contains('active')) {
if (this.monitorStatus !== 1) {
return;
}
@ -1646,8 +1651,7 @@ class DataMonitor extends HTMLElement {
const continuousInjectButton = e.target.closest('.action-button.inject-continuous');
if (continuousInjectButton) {
// 检查监控状态
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
if (!statusIndicator || !statusIndicator.classList.contains('active')) {
if (this.monitorStatus !== 1) {
return;
}
@ -1771,6 +1775,7 @@ class DataMonitor extends HTMLElement {
// 添加CSV文件注入相关的事件监听
const csvUploadButton = this.shadowRoot.getElementById('csvUploadButton');
const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton');
const fileInput = this.shadowRoot.getElementById('csvFileInput');
if (csvUploadButton && fileInput) {
@ -1782,6 +1787,177 @@ class DataMonitor extends HTMLElement {
if (fileInput) {
fileInput.addEventListener('change', this.handleCsvFileSelect);
}
if (csvInjectButton) {
csvInjectButton.addEventListener('click', async () => {
// 检查监控状态
if (this.monitorStatus !== 1) {
return;
}
// 如果按钮文本是"CSV停止注入",则停止注入
if (csvInjectButton.textContent.trim() === 'CSV停止注入') {
try {
const response = await fetch('/api/data-monitor/stop-csv-inject', {
method: 'POST'
});
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '停止CSV注入失败');
}
// 恢复按钮状态
csvUploadButton.disabled = false;
csvInjectButton.textContent = 'CSV数据注入';
csvInjectButton.style.display = 'flex';
// 恢复表格中除绘图按钮外的所有按钮的状态
const actionButtons = this.shadowRoot.querySelectorAll('.action-button:not(.plot)');
actionButtons.forEach(button => {
button.disabled = false;
});
// 恢复文件名显示
const csvFileName = this.shadowRoot.getElementById('csvFileName');
if (csvFileName) {
csvFileName.textContent = this.csvState.fileName;
}
// 更新CSV状态
this.csvState.isInjecting = false;
} catch (error) {
console.error('停止CSV注入失败:', error);
alert(`停止CSV注入失败: ${error.message}`);
}
return;
}
// 弹出确认对话框
if (!confirm('此操作将停止所有持续注入,是否继续?')) {
return;
}
try {
// 获取文件名元素
const csvFileName = this.shadowRoot.getElementById('csvFileName');
if (!csvFileName) {
throw new Error('找不到文件名元素');
}
// 停止所有持续注入
const continuousInjectButtons = this.shadowRoot.querySelectorAll('.action-button.inject-continuous.active');
for (const button of continuousInjectButtons) {
const interfaceName = button.getAttribute('data-interface');
const modelStructName = button.getAttribute('data-struct');
if (interfaceName && modelStructName) {
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 || '停止持续注入失败');
}
// 更新按钮状态
button.textContent = '连续注入';
button.classList.remove('active');
const tr = button.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;
// 更新表格数据状态
const row = this.tableData.find(r =>
r.InterfaceName === interfaceName &&
r.ModelStructName === modelStructName
);
if (row) {
row.isInjecting = false;
}
}
}
// 调用CSV注入接口
const response = await fetch('/api/data-monitor/inject-csv', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
structName: JSON.stringify(this.csvState.structNames),
csvFilePath: this.csvState.filePath
})
});
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '启动CSV注入失败');
}
// 禁用上传按钮
csvUploadButton.disabled = true;
// 更新注入按钮文本
csvInjectButton.textContent = 'CSV停止注入';
// 禁用表格中的除绘图按钮外的所有操作按钮
const actionButtons = this.shadowRoot.querySelectorAll('.action-button:not(.plot)');
actionButtons.forEach(button => {
button.disabled = true;
});
// 更新文件名显示
if (csvFileName) {
csvFileName.textContent = this.csvState.fileName + ' 正在注入中...';
}
// 更新CSV状态
this.csvState.isInjecting = true;
} catch (error) {
console.error('CSV注入失败:', error);
alert(`CSV注入失败: ${error.message}`);
}
});
}
// 恢复CSV状态
if (this.csvState.fileName) {
const csvFileName = this.shadowRoot.getElementById('csvFileName');
if (csvFileName) {
csvFileName.textContent = this.csvState.fileName;
if (this.csvState.isInjecting) {
csvFileName.textContent += ' 正在注入中...';
}
}
const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton');
if (csvInjectButton) {
csvInjectButton.style.display = 'flex';
if (this.csvState.isInjecting) {
csvInjectButton.textContent = 'CSV停止注入';
csvUploadButton.disabled = true;
// 禁用表格中除绘图外的所有按钮
const actionButtons = this.shadowRoot.querySelectorAll('.action-button:not(.plot)');
actionButtons.forEach(button => {
button.disabled = true;
});
}
}
}
}
/**
@ -1861,10 +2037,7 @@ class DataMonitor extends HTMLElement {
);
// 如果当前正在监控中,并且这是该结构体的最后一个接口,停止对该结构体的监控
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const isMonitoring = statusIndicator && statusIndicator.classList.contains('active');
if (isMonitoring && sameStructInterfaces.length === 0) {
if (this.monitorStatus === 1 && sameStructInterfaces.length === 0) {
await fetch('/api/data-monitor/stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@ -1922,8 +2095,6 @@ class DataMonitor extends HTMLElement {
throw new Error(result.message || '文件上传失败');
}
console.log('CSV文件上传成功:', result.file.name);
// 验证CSV文件头部
const validateResponse = await fetch(`/api/filesystem/validate-csv-headers?filename=${encodeURIComponent(result.file.name)}`);
if (!validateResponse.ok) {
@ -1938,6 +2109,7 @@ class DataMonitor extends HTMLElement {
// 检查每个头部是否在接口表中
const missingInterfaces = [];
const invalidInterfaces = [];
const structNames = []; // 用于按顺序收集结构体名称
// 检查第一个接口(时间)是否在接口表中
const firstHeader = validateResult.headers[0];
@ -1951,11 +2123,14 @@ class DataMonitor extends HTMLElement {
// 检查其他接口是否在接口表中
for (let i = 1; i < validateResult.headers.length; i++) {
const header = validateResult.headers[i];
const exists = this.interfaces.some(interfaceItem =>
const interfaceInfo = this.interfaces.find(interfaceItem =>
interfaceItem.InterfaceName === header
);
if (!exists) {
if (!interfaceInfo) {
missingInterfaces.push(header);
} else {
// 按顺序收集结构体名称
structNames.push(interfaceInfo.ModelStructName);
}
}
@ -1981,14 +2156,17 @@ class DataMonitor extends HTMLElement {
console.error('删除验证失败的文件时出错:', deleteError);
}
throw new Error(errorMessages.join('\n\n'));
}
console.log('CSV文件头部验证通过');
}
// 更新文件名显示
const csvFileName = this.shadowRoot.getElementById('csvFileName');
if (csvFileName) {
csvFileName.textContent = result.file.name;
// 更新CSV状态
this.csvState.fileName = result.file.name;
this.csvState.structNames = structNames;
this.csvState.filePath = result.file.path;
}
// 显示数据注入按钮

View File

@ -336,7 +336,7 @@ router.post('/inject-csv', async (req, res) => {
if (!structName || !csvFilePath) {
return res.status(400).json({
success: false,
message: '结构体名称、CSV文件路径和注入次数不能为空'
message: '结构体名称、CSV文件路径不能为空'
});
}