diff --git a/.gitignore b/.gitignore index 82c06ca..4fc4ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,8 +34,3 @@ # CMakeBuild build/ - -#log -log/ -logs/ -Packages/ diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 67fceec..a0d80ed 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/XNMonitorServer/CSVDataInjectThread.cpp b/XNMonitorServer/CSVDataInjectThread.cpp index 179c4ec..3775055 100644 --- a/XNMonitorServer/CSVDataInjectThread.cpp +++ b/XNMonitorServer/CSVDataInjectThread.cpp @@ -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(now - startTime).count(); + auto targetTime = startTime + std::chrono::milliseconds(nextTime); if (now < targetTime) { std::this_thread::sleep_until(targetTime); diff --git a/XNMonitorServer/XNMonitorInterface.cpp b/XNMonitorServer/XNMonitorInterface.cpp index 1f9706b..b51b93a 100644 --- a/XNMonitorServer/XNMonitorInterface.cpp +++ b/XNMonitorServer/XNMonitorInterface.cpp @@ -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; } diff --git a/XNMonitorServer/XNMonitorInterface.h b/XNMonitorServer/XNMonitorInterface.h index 4ececd1..9b03bc9 100644 --- a/XNMonitorServer/XNMonitorInterface.h +++ b/XNMonitorServer/XNMonitorInterface.h @@ -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 错误信息 diff --git a/XNSimHtml/components/data-monitor.js b/XNSimHtml/components/data-monitor.js index 5fb368d..8fdb351 100644 --- a/XNSimHtml/components/data-monitor.js +++ b/XNSimHtml/components/data-monitor.js @@ -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 { `; - 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; } // 显示数据注入按钮 diff --git a/XNSimHtml/routes/DataMonitor.js b/XNSimHtml/routes/DataMonitor.js index 6621d0e..59552db 100644 --- a/XNSimHtml/routes/DataMonitor.js +++ b/XNSimHtml/routes/DataMonitor.js @@ -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文件路径不能为空' }); }