diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db
index e066432..2bd0ecd 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 2105a62..3329bcd 100644
--- a/XNSimHtml/components/data-monitor.js
+++ b/XNSimHtml/components/data-monitor.js
@@ -23,6 +23,12 @@ class DataMonitor extends HTMLElement {
this.handleTreeItemDblClick = this.handleTreeItemDblClick.bind(this);
this.handlePlot = this.handlePlot.bind(this);
this.handleDelete = this.handleDelete.bind(this);
+ this.handleCsvInject = this.handleCsvInject.bind(this);
+ this.handleModalClose = this.handleModalClose.bind(this);
+ this.handleModalCancel = this.handleModalCancel.bind(this);
+ this.handleModalConfirm = this.handleModalConfirm.bind(this);
+ this.handleFileInput = this.handleFileInput.bind(this);
+ this.validateCsvFile = this.validateCsvFile.bind(this);
// 确保 FloatingChartWindow 组件已注册
if (!customElements.get('floating-chart-window')) {
@@ -1115,6 +1121,189 @@ class DataMonitor extends HTMLElement {
background-color: #f5f5f5;
cursor: not-allowed;
}
+
+ .csv-inject-button {
+ padding: 6px 12px;
+ background: #1890ff;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ transition: all 0.3s;
+ }
+
+ .csv-inject-button:hover {
+ background: #40a9ff;
+ }
+
+ .csv-inject-button:disabled {
+ background: #d9d9d9;
+ cursor: not-allowed;
+ }
+
+ .modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: none;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ }
+
+ .modal-overlay.active {
+ display: flex;
+ }
+
+ .modal-content {
+ background: white;
+ border-radius: 8px;
+ padding: 24px;
+ width: 400px;
+ max-width: 90%;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ }
+
+ .modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ }
+
+ .modal-title {
+ font-size: 18px;
+ font-weight: 500;
+ color: #262626;
+ margin: 0;
+ }
+
+ .modal-close {
+ background: none;
+ border: none;
+ font-size: 20px;
+ color: #999;
+ cursor: pointer;
+ padding: 4px;
+ line-height: 1;
+ }
+
+ .modal-close:hover {
+ color: #666;
+ }
+
+ .modal-body {
+ margin-bottom: 20px;
+ }
+
+ .form-group {
+ margin-bottom: 16px;
+ }
+
+ .form-group label {
+ display: block;
+ margin-bottom: 8px;
+ color: #262626;
+ font-size: 14px;
+ }
+
+ .form-group input {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid #d9d9d9;
+ border-radius: 4px;
+ font-size: 14px;
+ box-sizing: border-box;
+ }
+
+ .form-group input:focus {
+ border-color: #1890ff;
+ outline: none;
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+ }
+
+ .modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ }
+
+ .modal-button {
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.3s;
+ }
+
+ .modal-button.primary {
+ background: #1890ff;
+ color: white;
+ border: none;
+ }
+
+ .modal-button.primary:hover {
+ background: #40a9ff;
+ }
+
+ .modal-button.secondary {
+ background: white;
+ color: #262626;
+ border: 1px solid #d9d9d9;
+ }
+
+ .modal-button.secondary:hover {
+ border-color: #1890ff;
+ color: #1890ff;
+ }
+
+ .file-input-wrapper {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+ }
+
+ .file-input-wrapper input[type="file"] {
+ position: absolute;
+ left: 0;
+ top: 0;
+ opacity: 0;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+ }
+
+ .file-input-trigger {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px;
+ border: 1px dashed #d9d9d9;
+ border-radius: 4px;
+ color: #666;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.3s;
+ }
+
+ .file-input-trigger:hover {
+ border-color: #1890ff;
+ color: #1890ff;
+ }
+
+ .file-name {
+ margin-top: 8px;
+ font-size: 14px;
+ color: #666;
+ word-break: break-all;
+ }
@@ -1562,6 +1789,61 @@ class DataMonitor extends HTMLElement {
}
}
});
+
+ // 添加CSV文件注入相关的事件监听
+ const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton');
+ const modal = this.shadowRoot.getElementById('csvModal');
+ const modalClose = this.shadowRoot.getElementById('modalClose');
+ const modalCancel = this.shadowRoot.getElementById('modalCancel');
+ const modalConfirm = this.shadowRoot.getElementById('modalConfirm');
+ const fileInput = this.shadowRoot.getElementById('csvFileInput');
+ const fileInputTrigger = this.shadowRoot.getElementById('fileInputTrigger');
+
+ if (csvInjectButton) {
+ csvInjectButton.addEventListener('click', this.handleCsvInject);
+ }
+
+ if (modalClose) {
+ modalClose.addEventListener('click', this.handleModalClose);
+ }
+
+ if (modalCancel) {
+ modalCancel.addEventListener('click', this.handleModalCancel);
+ }
+
+ if (modalConfirm) {
+ modalConfirm.addEventListener('click', this.handleModalConfirm);
+ }
+
+ if (fileInput) {
+ fileInput.addEventListener('change', this.handleFileInput);
+ }
+
+ if (fileInputTrigger) {
+ fileInputTrigger.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ fileInputTrigger.style.borderColor = '#1890ff';
+ });
+
+ fileInputTrigger.addEventListener('dragleave', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ fileInputTrigger.style.borderColor = '#d9d9d9';
+ });
+
+ fileInputTrigger.addEventListener('drop', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ fileInputTrigger.style.borderColor = '#d9d9d9';
+
+ const files = e.dataTransfer.files;
+ if (files.length > 0) {
+ fileInput.files = files;
+ this.handleFileInput({ target: fileInput });
+ }
+ });
+ }
}
/**
@@ -1669,6 +1951,94 @@ class DataMonitor extends HTMLElement {
}
}
+ /**
+ * @description 处理CSV文件注入按钮点击事件
+ */
+ handleCsvInject() {
+ const modal = this.shadowRoot.getElementById('csvModal');
+ if (modal) {
+ modal.classList.add('active');
+ }
+ }
+
+ /**
+ * @description 处理模态框关闭事件
+ */
+ handleModalClose() {
+ const modal = this.shadowRoot.getElementById('csvModal');
+ if (modal) {
+ modal.classList.remove('active');
+ }
+ }
+
+ /**
+ * @description 处理模态框取消按钮点击事件
+ */
+ handleModalCancel() {
+ this.handleModalClose();
+ }
+
+ /**
+ * @description 处理模态框确认按钮点击事件
+ */
+ async handleModalConfirm() {
+ const fileInput = this.shadowRoot.getElementById('csvFileInput');
+ const frequencyInput = this.shadowRoot.getElementById('injectFrequency');
+ const durationInput = this.shadowRoot.getElementById('injectDuration');
+
+ if (!fileInput.files.length) {
+ alert('请选择CSV文件');
+ return;
+ }
+
+ const file = fileInput.files[0];
+ const frequency = parseInt(frequencyInput.value);
+ const duration = parseInt(durationInput.value);
+
+ if (!this.validateCsvFile(file)) {
+ return;
+ }
+
+ try {
+ // TODO: 实现CSV文件注入逻辑
+ console.log('CSV文件注入:', {
+ file: file.name,
+ frequency,
+ duration
+ });
+
+ this.handleModalClose();
+ } catch (error) {
+ console.error('CSV文件注入失败:', error);
+ alert(`CSV文件注入失败: ${error.message}`);
+ }
+ }
+
+ /**
+ * @description 处理文件输入事件
+ * @param {Event} event - 文件输入事件
+ */
+ handleFileInput(event) {
+ const file = event.target.files[0];
+ const fileName = this.shadowRoot.getElementById('fileName');
+
+ if (file) {
+ fileName.textContent = file.name;
+ } else {
+ fileName.textContent = '';
+ }
+ }
+
+ /**
+ * @description 验证CSV文件
+ * @param {File} file - CSV文件对象
+ * @returns {boolean} 是否验证通过
+ */
+ validateCsvFile(file) {
+ // TODO: 实现CSV文件验证逻辑
+ return true;
+ }
+
disconnectedCallback() {
this.isActive = false;
// 清理所有图表窗口
diff --git a/XNSimHtml/components/run-log.js b/XNSimHtml/components/run-log.js
index fad0f5b..13ccfe3 100644
--- a/XNSimHtml/components/run-log.js
+++ b/XNSimHtml/components/run-log.js
@@ -289,7 +289,7 @@ class RunLog extends HTMLElement {
const logFiles = [];
for (const fileName of files) {
if (fileName.endsWith('.log')) {
- const filePath = `${logDirPath}/${fileName}`;
+ const filePath = fileName; // 直接使用文件名,因为后端已经处理了完整路径
const fileStats = await this.getFileStats(filePath);
// 格式化修改时间
@@ -317,7 +317,7 @@ class RunLog extends HTMLElement {
// 读取目录内容
async readLogDirectory(dirPath) {
return new Promise((resolve, reject) => {
- fetch(`/api/filesystem/readdir?path=${encodeURIComponent(dirPath)}`)
+ fetch('/api/filesystem/readdir') // 移除路径参数,因为后端已经处理了
.then(response => {
if (!response.ok) {
throw new Error(`读取目录失败: ${response.status}`);
@@ -337,7 +337,7 @@ class RunLog extends HTMLElement {
// 获取文件状态信息
async getFileStats(filePath) {
return new Promise((resolve, reject) => {
- fetch(`/api/filesystem/stat?path=${encodeURIComponent(filePath)}`)
+ fetch(`/api/filesystem/stat?path=${encodeURIComponent(filePath)}`) // 只传递文件名
.then(response => {
if (!response.ok) {
throw new Error(`获取文件信息失败: ${response.status}`);
diff --git a/XNSimHtml/main.html b/XNSimHtml/main.html
index 33ebbfc..4b65df4 100644
--- a/XNSimHtml/main.html
+++ b/XNSimHtml/main.html
@@ -600,6 +600,19 @@
return;
}
+ // 处理网络监控标签页
+ if (title === '网络监控') {
+ const id = 'network-monitor';
+ tabsContainer.createTab(id, title, icon, parentText, parentTool);
+ return;
+ }
+
+ // 处理QTG标签页
+ if (title === 'QTG') {
+ const id = 'qtg-monitor';
+ tabsContainer.createTab(id, title, icon, parentText, parentTool);
+ return;
+ }
// 默认情况下使用标题转换为ID
const id = title.toLowerCase().replace(/\s+/g, '-');
tabsContainer.createTab(id, title, icon, parentText, parentTool);
@@ -625,7 +638,9 @@
'用户管理': 'users',
'模型开发': 'cube',
'服务开发': 'settings',
- '系统日志': 'file-text'
+ '系统日志': 'file-text',
+ '网络监控': 'network',
+ 'QTG': 'qtg'
};
return iconMap[title] || parentTool || 'con';
diff --git a/XNSimHtml/routes/filesystem.js b/XNSimHtml/routes/filesystem.js
index 1a8bc8f..c82da92 100644
--- a/XNSimHtml/routes/filesystem.js
+++ b/XNSimHtml/routes/filesystem.js
@@ -1,37 +1,31 @@
const express = require('express');
const router = express.Router();
const fs = require('fs').promises;
+const path = require('path');
const { getActualLogPath } = require('../utils/file-utils');
// 读取目录内容
router.get('/readdir', async (req, res) => {
try {
- const dirPath = req.query.path;
-
- if (!dirPath) {
- return res.status(400).json({ error: '缺少路径参数' });
- }
-
- // 获取实际的文件系统路径
- const actualPath = await getActualLogPath(dirPath);
+ // 获取实际的日志目录路径
+ const logDirPath = await getActualLogPath();
// 安全检查
- if (!actualPath) {
+ if (!logDirPath) {
return res.status(403).json({ error: '无权访问该目录' });
}
// 检查目录是否存在
try {
- const stats = await fs.stat(actualPath);
+ const stats = await fs.stat(logDirPath);
if (!stats.isDirectory()) {
return res.status(400).json({ error: '指定的路径不是目录' });
}
} catch (statError) {
- // 如果目录不存在但是请求的是/log,尝试创建它
- if (statError.code === 'ENOENT' && dirPath === '/log') {
+ // 如果目录不存在,尝试创建它
+ if (statError.code === 'ENOENT') {
try {
- // 确保XNCore下有log目录
- await fs.mkdir(actualPath, { recursive: true });
+ await fs.mkdir(logDirPath, { recursive: true });
} catch (mkdirError) {
console.error('创建日志目录失败:', mkdirError);
return res.status(500).json({ error: '创建日志目录失败' });
@@ -42,7 +36,7 @@ router.get('/readdir', async (req, res) => {
}
// 读取目录内容
- const files = await fs.readdir(actualPath);
+ const files = await fs.readdir(logDirPath);
// 返回文件列表
res.json({ files });
@@ -64,22 +58,27 @@ router.get('/readdir', async (req, res) => {
// 获取文件状态信息
router.get('/stat', async (req, res) => {
try {
- const filePath = req.query.path;
-
- if (!filePath) {
- return res.status(400).json({ error: '缺少路径参数' });
+ const fileName = req.query.path;
+ if (!fileName) {
+ return res.status(400).json({ error: '未提供文件名' });
}
+
+ // 获取实际的日志目录路径
+ const logDirPath = await getActualLogPath();
+ if (!logDirPath) {
+ return res.status(403).json({ error: '无权访问该文件' });
+ }
+
+ // 构建完整的文件路径
+ const filePath = path.join(logDirPath, fileName);
- // 获取实际的文件系统路径
- const actualPath = await getActualLogPath(filePath);
-
- // 安全检查
- if (!actualPath) {
+ // 安全检查:确保文件在日志目录内
+ if (!filePath.startsWith(logDirPath)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 获取文件状态
- const stats = await fs.stat(actualPath);
+ const stats = await fs.stat(filePath);
// 返回文件信息
res.json({
@@ -109,27 +108,32 @@ router.get('/stat', async (req, res) => {
// 读取文件内容
router.get('/readFile', async (req, res) => {
try {
- const filePath = req.query.path;
-
- if (!filePath) {
- return res.status(400).json({ error: '缺少路径参数' });
+ const fileName = req.query.path;
+ if (!fileName) {
+ return res.status(400).json({ error: '未提供文件名' });
}
+
+ // 获取实际的日志目录路径
+ const logDirPath = await getActualLogPath();
+ if (!logDirPath) {
+ return res.status(403).json({ error: '无权访问该文件' });
+ }
+
+ // 构建完整的文件路径
+ const filePath = path.join(logDirPath, fileName);
- // 获取实际的文件系统路径
- const actualPath = await getActualLogPath(filePath);
-
- // 安全检查
- if (!actualPath) {
+ // 安全检查:确保文件在日志目录内
+ if (!filePath.startsWith(logDirPath)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 只允许访问.log文件
- if (!actualPath.endsWith('.log')) {
+ if (!fileName.endsWith('.log')) {
return res.status(403).json({ error: '只能访问日志文件' });
}
// 获取文件状态
- const stats = await fs.stat(actualPath);
+ const stats = await fs.stat(filePath);
// 检查文件大小,限制读取过大的文件
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
@@ -138,7 +142,7 @@ router.get('/readFile', async (req, res) => {
}
// 读取文件内容
- const content = await fs.readFile(actualPath, 'utf-8');
+ const content = await fs.readFile(filePath, 'utf-8');
// 设置响应头
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
diff --git a/XNSimHtml/routes/idl.js b/XNSimHtml/routes/idl.js
deleted file mode 100644
index 9edd1df..0000000
--- a/XNSimHtml/routes/idl.js
+++ /dev/null
@@ -1,189 +0,0 @@
-const express = require('express');
-const router = express.Router();
-const fs = require('fs').promises;
-const path = require('path');
-
-// 获取XNCore环境变量
-const xnCorePath = process.env.XNCore || '';
-
-// 默认IDL文件保存目录,使用XNCore环境变量下的IDL子文件夹
-const IDL_BASE_DIR = xnCorePath ? path.join(xnCorePath, 'IDL') : path.join(process.cwd(), 'idl_files');
-
-// 确保IDL文件目录存在
-async function ensureIdlDirExists() {
- try {
- await fs.mkdir(IDL_BASE_DIR, { recursive: true });
- } catch (error) {
- console.error('创建IDL目录失败:', error);
- }
-}
-
-// 初始化确保目录存在
-ensureIdlDirExists();
-
-// 安全地获取文件路径,避免路径遍历攻击
-function getSafePath(filePath) {
- // 确保文件路径以.idl结尾
- if (!filePath.toLowerCase().endsWith('.idl')) {
- filePath += '.idl';
- }
-
- // 使用path.join确保路径在IDL_BASE_DIR下
- const safePath = path.join(IDL_BASE_DIR, path.basename(filePath));
-
- return safePath;
-}
-
-// 列出所有IDL文件
-router.get('/list', async (req, res) => {
- try {
- // 确保目录存在
- await ensureIdlDirExists();
-
- // 读取目录
- const files = await fs.readdir(IDL_BASE_DIR);
-
- // 过滤出.idl文件
- const idlFiles = files.filter(file => file.toLowerCase().endsWith('.idl'));
-
- // 获取文件信息
- const fileInfos = await Promise.all(idlFiles.map(async (file) => {
- const filePath = path.join(IDL_BASE_DIR, file);
- const stats = await fs.stat(filePath);
-
- return {
- name: file,
- size: stats.size,
- modified: stats.mtime
- };
- }));
-
- res.json({ files: fileInfos });
- } catch (error) {
- console.error('列出IDL文件失败:', error);
- res.status(500).json({ error: '列出IDL文件失败', message: error.message });
- }
-});
-
-// 读取IDL文件内容
-router.get('/read', async (req, res) => {
- try {
- const filename = req.query.filename;
-
- if (!filename) {
- return res.status(400).json({ error: '缺少文件名参数' });
- }
-
- const filePath = getSafePath(filename);
-
- // 读取文件内容
- const content = await fs.readFile(filePath, 'utf-8');
-
- res.json({ content, filename: path.basename(filePath) });
- } catch (error) {
- console.error('读取IDL文件失败:', error);
-
- if (error.code === 'ENOENT') {
- return res.status(404).json({ error: '文件不存在' });
- }
-
- res.status(500).json({ error: '读取IDL文件失败', message: error.message });
- }
-});
-
-// 保存IDL文件内容
-router.post('/save', async (req, res) => {
- try {
- const { filename, content } = req.body;
-
- if (!filename || content === undefined) {
- return res.status(400).json({ error: '缺少必要参数' });
- }
-
- // 确保目录存在
- await ensureIdlDirExists();
-
- const filePath = getSafePath(filename);
-
- // 写入文件内容
- await fs.writeFile(filePath, content, 'utf-8');
-
- res.json({ success: true, filename: path.basename(filePath) });
- } catch (error) {
- console.error('保存IDL文件失败:', error);
- res.status(500).json({ error: '保存IDL文件失败', message: error.message });
- }
-});
-
-// 新建IDL文件
-router.post('/create', async (req, res) => {
- try {
- const { filename } = req.body;
-
- if (!filename) {
- return res.status(400).json({ error: '缺少文件名参数' });
- }
-
- // 确保目录存在
- await ensureIdlDirExists();
-
- const filePath = getSafePath(filename);
-
- // 检查文件是否已存在
- try {
- await fs.access(filePath);
- return res.status(409).json({ error: '文件已存在' });
- } catch (accessError) {
- // 文件不存在,可以创建
- }
-
- // 创建具有默认内容的IDL文件
- const defaultContent = `module XNSim
-{
- struct YourStruct
- {
- // 在此添加字段
- };
-};`;
-
- // 写入文件内容
- await fs.writeFile(filePath, defaultContent, 'utf-8');
-
- res.json({
- success: true,
- filename: path.basename(filePath),
- content: defaultContent
- });
- } catch (error) {
- console.error('创建IDL文件失败:', error);
- res.status(500).json({ error: '创建IDL文件失败', message: error.message });
- }
-});
-
-// 删除IDL文件
-router.delete('/delete', async (req, res) => {
- try {
- const filename = req.query.filename;
-
- if (!filename) {
- return res.status(400).json({ error: '缺少文件名参数' });
- }
-
- const filePath = getSafePath(filename);
-
- // 删除文件
- await fs.unlink(filePath);
-
- res.json({ success: true, filename: path.basename(filePath) });
- } catch (error) {
- console.error('删除IDL文件失败:', error);
-
- if (error.code === 'ENOENT') {
- return res.status(404).json({ error: '文件不存在' });
- }
-
- res.status(500).json({ error: '删除IDL文件失败', message: error.message });
- }
-});
-
-module.exports = router;
\ No newline at end of file
diff --git a/XNSimHtml/routes/project-model.js b/XNSimHtml/routes/project-model.js
deleted file mode 100644
index 976821e..0000000
--- a/XNSimHtml/routes/project-model.js
+++ /dev/null
@@ -1,174 +0,0 @@
-const express = require('express');
-const router = express.Router();
-const path = require('path');
-const fs = require('fs').promises;
-const xml2js = require('xml2js');
-const { getProjectModelPath, isPathSafe, ensureDirectoryExists } = require('../utils/file-utils');
-
-// 递归读取目录下的所有.xnprj文件
-async function readProjectFiles(dir) {
- const results = [];
-
- try {
- // 确保目录存在
- try {
- await fs.access(dir);
- } catch (error) {
- if (error.code === 'ENOENT') {
- console.warn(`目录不存在: ${dir}`);
- return results;
- }
- throw error;
- }
-
- // 读取目录内容
- const entries = await fs.readdir(dir, { withFileTypes: true });
-
- // 处理每个条目
- for (const entry of entries) {
- const fullPath = path.join(dir, entry.name);
-
- if (entry.isDirectory()) {
- // 递归读取子目录
- const nestedResults = await readProjectFiles(fullPath);
- results.push(...nestedResults);
- } else if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.xnprj') {
- // 读取文件内容
- try {
- const content = await fs.readFile(fullPath, 'utf-8');
-
- // 解析XML
- try {
- const parser = new xml2js.Parser({ explicitArray: false });
- const result = await parser.parseStringPromise(content);
-
- // 提取关键信息
- const modelInfo = {
- path: fullPath,
- name: (result.Model && result.Model.n) || path.basename(entry.name, '.xnprj'),
- description: (result.Model && result.Model.Description) || '',
- author: (result.Model && result.Model.Author) || '',
- version: (result.Model && result.Model.Version) || '',
- createTime: (result.Model && result.Model.CreateTime) || '',
- changeTime: (result.Model && result.Model.ChangeTime) || '',
- relativePath: path.relative(getProjectModelPath(), fullPath)
- };
-
- results.push(modelInfo);
- } catch (xmlError) {
- console.error(`解析XML文件失败: ${fullPath}`, xmlError);
- // 即使解析失败,也返回基本信息
- results.push({
- path: fullPath,
- name: path.basename(entry.name, '.xnprj'),
- description: '无法解析的XML文件',
- error: true,
- relativePath: path.relative(getProjectModelPath(), fullPath)
- });
- }
- } catch (readError) {
- console.error(`读取文件失败: ${fullPath}`, readError);
- results.push({
- path: fullPath,
- name: path.basename(entry.name, '.xnprj'),
- description: '无法读取的文件',
- error: true,
- relativePath: path.relative(getProjectModelPath(), fullPath)
- });
- }
- }
- }
- } catch (error) {
- console.error(`扫描目录失败: ${dir}`, error);
- }
-
- return results;
-}
-
-// 获取所有项目模型信息
-router.get('/project-models', async (req, res) => {
- try {
- const projectModelDir = getProjectModelPath();
-
- // 检查XNCore是否设置
- if (!projectModelDir) {
- console.error('XNCore环境变量未设置,无法获取项目模型目录');
- return res.status(500).json({
- error: 'XNCore环境变量未设置',
- message: '无法获取项目模型目录'
- });
- }
-
- // 确保目录存在
- await ensureDirectoryExists(projectModelDir);
-
- // 读取所有项目模型文件
- const modelFiles = await readProjectFiles(projectModelDir);
-
- res.json(modelFiles);
- } catch (error) {
- console.error('获取项目模型文件列表失败:', error);
- res.status(500).json({ error: '获取项目模型文件列表失败', message: error.message });
- }
-});
-
-// 获取特定项目模型文件内容
-router.get('/project-model-content', async (req, res) => {
- try {
- const filePath = req.query.path;
-
- if (!filePath) {
- return res.status(400).json({ error: '缺少路径参数' });
- }
-
- // 安全检查 - 确保只能访问Project/Model目录下的文件
- const projectModelDir = getProjectModelPath();
- if (!isPathSafe(filePath, projectModelDir)) {
- return res.status(403).json({ error: '无权访问该文件' });
- }
-
- // 检查文件后缀,只允许.xnprj
- const ext = path.extname(filePath).toLowerCase();
- if (ext !== '.xnprj') {
- return res.status(403).json({ error: '只能访问项目模型文件(.xnprj)' });
- }
-
- // 检查文件是否存在
- try {
- await fs.access(filePath);
- } catch (error) {
- if (error.code === 'ENOENT') {
- return res.status(404).json({ error: '文件不存在' });
- }
- throw error;
- }
-
- // 获取文件状态
- const stats = await fs.stat(filePath);
-
- // 检查是否为文件
- if (!stats.isFile()) {
- return res.status(400).json({ error: '请求的路径不是文件' });
- }
-
- // 大文件检查
- const MAX_FILE_SIZE = 1024 * 1024; // 1MB限制
- if (stats.size > MAX_FILE_SIZE) {
- return res.status(413).json({ error: '文件过大,无法读取' });
- }
-
- // 读取文件内容
- const content = await fs.readFile(filePath, 'utf-8');
-
- // 设置响应头
- res.setHeader('Content-Type', 'text/plain; charset=utf-8');
-
- // 返回文件内容
- res.send(content);
- } catch (error) {
- console.error('读取项目模型文件内容失败:', error);
- res.status(500).json({ error: '读取项目模型文件内容失败', message: error.message });
- }
-});
-
-module.exports = router;
\ No newline at end of file
diff --git a/XNSimHtml/routes/scenario-config.js b/XNSimHtml/routes/scenario-config.js
deleted file mode 100644
index a000c69..0000000
--- a/XNSimHtml/routes/scenario-config.js
+++ /dev/null
@@ -1,372 +0,0 @@
-const express = require('express');
-const router = express.Router();
-const path = require('path');
-const fs = require('fs').promises;
-const { getScenarioPath, isPathSafe, ensureDirectoryExists, validateXml } = require('../utils/file-utils');
-
-// 获取场景文件列表
-router.get('/scenario-files', async (req, res) => {
- try {
- const scenarioDir = getScenarioPath();
-
- // 检查XNCore是否设置
- if (!scenarioDir) {
- console.error('XNCore环境变量未设置,无法获取场景目录');
- return res.status(500).json({
- error: 'XNCore环境变量未设置',
- message: '无法获取场景目录'
- });
- }
-
- //console.log('获取场景文件,目录路径:', scenarioDir);
-
- // 检查并创建目录(如果不存在)
- try {
- await ensureDirectoryExists(scenarioDir);
- } catch (error) {
- console.error('场景目录访问失败:', error);
- return res.status(500).json({
- error: '场景目录不存在且无法创建',
- message: error.message
- });
- }
-
- // 读取目录内容
- const files = await fs.readdir(scenarioDir);
- //console.log('目录中的文件数量:', files.length);
-
- // 过滤出.sce和.xml文件
- const scenarioFiles = [];
- for (const file of files) {
- const ext = path.extname(file).toLowerCase();
- if (ext === '.sce' || ext === '.xml') {
- const filePath = path.join(scenarioDir, file);
- const stats = await fs.stat(filePath);
-
- if (stats.isFile()) {
- scenarioFiles.push({
- name: file,
- path: filePath,
- size: stats.size,
- mtime: stats.mtime
- });
- }
- }
- }
-
- //console.log('找到的场景文件数量:', scenarioFiles.length);
- res.json(scenarioFiles);
- } catch (error) {
- console.error('获取场景文件列表失败:', error);
- res.status(500).json({ error: '获取场景文件列表失败', message: error.message });
- }
-});
-
-// 获取文件内容
-router.get('/file-content', async (req, res) => {
- try {
- const filePath = req.query.path;
-
- if (!filePath) {
- return res.status(400).json({ error: '缺少路径参数' });
- }
-
- // 安全检查 - 确保只能访问Scenario目录下的文件
- const scenarioDir = getScenarioPath();
- if (!isPathSafe(filePath, scenarioDir)) {
- return res.status(403).json({ error: '无权访问该文件' });
- }
-
- // 检查文件后缀,只允许.sce和.xml
- const ext = path.extname(filePath).toLowerCase();
- if (ext !== '.sce' && ext !== '.xml') {
- return res.status(403).json({ error: '只能访问场景文件(.sce或.xml)' });
- }
-
- // 检查文件是否存在
- try {
- await fs.access(filePath);
- } catch (error) {
- if (error.code === 'ENOENT') {
- // 如果是.sce文件不存在,尝试创建空文件
- if (ext === '.sce') {
- try {
- await fs.writeFile(filePath, '', 'utf-8');
- // 返回空内容
- return res.send('');
- } catch (writeError) {
- console.error('创建.sce文件失败:', writeError);
- return res.status(500).json({ error: '创建文件失败', message: writeError.message });
- }
- } else {
- return res.status(404).json({ error: '文件不存在' });
- }
- } else {
- throw error;
- }
- }
-
- // 获取文件状态
- const stats = await fs.stat(filePath);
-
- // 检查是否为文件
- if (!stats.isFile()) {
- return res.status(400).json({ error: '请求的路径不是文件' });
- }
-
- // 大文件检查
- const MAX_FILE_SIZE = 1024 * 1024; // 1MB限制
- if (stats.size > MAX_FILE_SIZE) {
- return res.status(413).json({ error: '文件过大,无法读取' });
- }
-
- // 读取文件内容
- const content = await fs.readFile(filePath, 'utf-8');
-
- // 设置响应头
- res.setHeader('Content-Type', 'text/plain; charset=utf-8');
-
- // 返回文件内容
- res.send(content);
- } catch (error) {
- console.error('读取文件内容失败:', error);
- res.status(500).json({ error: '读取文件内容失败', message: error.message });
- }
-});
-
-// 保存文件内容
-router.post('/save-file', async (req, res) => {
- try {
- const { path: filePath, content } = req.body;
-
- if (!filePath || content === undefined) {
- return res.status(400).json({ error: '缺少必要参数' });
- }
-
- // 安全检查 - 确保只能访问Scenario目录下的文件
- const scenarioDir = getScenarioPath();
- if (!isPathSafe(filePath, scenarioDir)) {
- return res.status(403).json({ error: '无权访问该文件' });
- }
-
- // 检查文件后缀,只允许.sce和.xml
- const ext = path.extname(filePath).toLowerCase();
- if (ext !== '.sce' && ext !== '.xml') {
- return res.status(403).json({ error: '只能修改场景文件(.sce或.xml)' });
- }
-
- // 确保目录存在
- await ensureDirectoryExists(path.dirname(filePath));
-
- // 检查XML格式是否有效(对于.xml和.sce文件)
- if (content.trim()) { // 只有当内容不为空时才检查
- const validation = await validateXml(content);
- if (validation !== true) {
- return res.status(400).json(validation);
- }
- }
-
- // 写入文件内容
- await fs.writeFile(filePath, content, 'utf-8');
-
- res.json({ success: true, message: '文件保存成功' });
- } catch (error) {
- console.error('保存文件内容失败:', error);
- res.status(500).json({ error: '保存文件内容失败', message: error.message });
- }
-});
-
-// 检查文件是否存在
-router.get('/file-exists', async (req, res) => {
- try {
- const filePath = req.query.path;
-
- if (!filePath) {
- return res.status(400).json({ error: '缺少路径参数' });
- }
-
- // 安全检查 - 确保只能访问Scenario目录下的文件
- const scenarioDir = getScenarioPath();
- if (!isPathSafe(filePath, scenarioDir)) {
- return res.status(403).json({ error: '无权访问该文件' });
- }
-
- // 检查文件后缀,只允许.sce和.xml
- const ext = path.extname(filePath).toLowerCase();
- if (ext !== '.sce' && ext !== '.xml') {
- return res.status(403).json({ error: '只能检查场景文件(.sce或.xml)' });
- }
-
- // 检查文件是否存在
- try {
- await fs.access(filePath);
- res.json({ exists: true });
- } catch (error) {
- if (error.code === 'ENOENT') {
- res.json({ exists: false });
- } else {
- throw error;
- }
- }
- } catch (error) {
- console.error('检查文件存在性失败:', error);
- res.status(500).json({ error: '检查文件存在性失败', message: error.message });
- }
-});
-
-// 创建新配置文件
-router.post('/create-config-file', async (req, res) => {
- try {
- const { fileName } = req.body;
-
- if (!fileName) {
- return res.status(400).json({ error: '缺少文件名参数' });
- }
-
- // 获取Scenario目录
- const scenarioDir = getScenarioPath();
- if (!scenarioDir) {
- return res.status(500).json({ error: 'XNCore环境变量未设置' });
- }
-
- // 设置文件路径
- const filePath = path.join(scenarioDir, fileName);
-
- // 检查文件后缀,只允许.sce和.xml
- const ext = path.extname(fileName).toLowerCase();
- if (ext !== '.sce' && ext !== '.xml') {
- return res.status(403).json({ error: '只能创建场景文件(.sce或.xml)' });
- }
-
- // 确保目录存在
- await ensureDirectoryExists(scenarioDir);
-
- // 检查文件是否已存在
- try {
- await fs.access(filePath);
- // 文件已存在
- return res.status(409).json({ error: '文件已存在' });
- } catch (error) {
- // 文件不存在,可以继续创建
- if (error.code !== 'ENOENT') {
- throw error;
- }
- }
-
- // 创建基本的XML模板
- const xmlContent = `
-
-
-
-
-
-
-
-
-
-
-`;
-
- // 写入文件内容
- await fs.writeFile(filePath, xmlContent, 'utf-8');
-
- res.status(201).json({
- success: true,
- message: '文件创建成功',
- path: filePath,
- content: xmlContent
- });
- } catch (error) {
- console.error('创建新配置文件失败:', error);
- res.status(500).json({ error: '创建新配置文件失败', message: error.message });
- }
-});
-
-// 另存为文件
-router.post('/save-as-file', async (req, res) => {
- try {
- const { fileName, content, currentFile, overwrite } = req.body;
-
- if (!fileName || content === undefined) {
- return res.status(400).json({ error: '缺少必要参数' });
- }
-
- // 获取Scenario目录
- const scenarioDir = getScenarioPath();
- if (!scenarioDir) {
- return res.status(500).json({ error: 'XNCore环境变量未设置' });
- }
-
- // 设置目标文件路径
- const targetPath = path.join(scenarioDir, fileName);
-
- // 检查文件后缀,只允许.sce和.xml
- const ext = path.extname(fileName).toLowerCase();
- if (ext !== '.sce' && ext !== '.xml') {
- return res.status(403).json({ error: '只能保存场景文件(.sce或.xml)' });
- }
-
- // 确保目录存在
- await ensureDirectoryExists(path.dirname(targetPath));
-
- // 如果路径与当前文件相同,直接保存
- if (currentFile && targetPath === currentFile) {
- await fs.writeFile(targetPath, content, 'utf-8');
- return res.json({
- success: true,
- message: '文件保存成功',
- path: targetPath
- });
- }
-
- // 检查文件是否已存在
- try {
- await fs.access(targetPath);
-
- // 文件已存在,但没有覆盖标记
- if (!overwrite) {
- return res.status(409).json({
- error: '文件已存在',
- code: 'FILE_EXISTS'
- });
- }
- } catch (error) {
- // 文件不存在,可以直接创建
- if (error.code !== 'ENOENT') {
- throw error;
- }
- }
-
- // 检查XML格式是否有效
- if (content.trim()) {
- const validation = await validateXml(content);
- if (validation !== true) {
- return res.status(400).json(validation);
- }
- }
-
- // 写入文件内容
- await fs.writeFile(targetPath, content, 'utf-8');
-
- res.json({
- success: true,
- message: '文件保存成功',
- path: targetPath
- });
- } catch (error) {
- console.error('另存为文件失败:', error);
- res.status(500).json({ error: '另存为文件失败', message: error.message });
- }
-});
-
-module.exports = router;
\ No newline at end of file
diff --git a/XNSimHtml/server.js b/XNSimHtml/server.js
index 5aec15b..8feedab 100644
--- a/XNSimHtml/server.js
+++ b/XNSimHtml/server.js
@@ -11,10 +11,7 @@ const authRoutes = require('./routes/auth');
const versionRoutes = require('./routes/versions');
const filesystemRoutes = require('./routes/filesystem');
const systemInfoRoutes = require('./routes/system-info');
-const scenarioRoutes = require('./routes/scenario-config');
const serviceApiRoutes = require('./routes/service-dev');
-const idlRoutes = require('./routes/idl');
-const projectModelRoutes = require('./routes/project-model');
const ataChaptersRoutes = require('./routes/model-dev');
const simulationRoutes = require('./routes/run-simulation');
const udpMonitorRoutes = require('./routes/udp-monitor');
@@ -83,10 +80,7 @@ app.use('/api', authRoutes);
app.use('/api', versionRoutes);
app.use('/api/filesystem', filesystemRoutes);
app.use('/api', systemInfoRoutes);
-app.use('/api', scenarioRoutes);
app.use('/api', serviceApiRoutes);
-app.use('/api/idl', idlRoutes);
-app.use('/api', projectModelRoutes);
app.use('/api', ataChaptersRoutes);
app.use('/api', simulationRoutes);
app.use('/api/udp-monitor', udpMonitorRoutes);
diff --git a/XNSimHtml/utils/file-utils.js b/XNSimHtml/utils/file-utils.js
index 3496bdc..1d5b401 100644
--- a/XNSimHtml/utils/file-utils.js
+++ b/XNSimHtml/utils/file-utils.js
@@ -49,14 +49,7 @@ function getXNCorePath() {
return xnCorePath;
}
-// 场景文件目录路径
-function getScenarioPath() {
- const xnCorePath = getXNCorePath();
- if (!xnCorePath) return '';
- return path.join(xnCorePath, 'Scenario');
-}
-
-// 项目模型文件目录路径
+// 模型项目文件目录路径
function getProjectModelPath() {
const xnCorePath = getXNCorePath();
if (!xnCorePath) return '';
@@ -70,6 +63,13 @@ function getModelPath() {
return path.join(xnCorePath, 'Models');
}
+//服务项目文件目录路径
+function getProjectServicePath() {
+ const xnCorePath = getXNCorePath();
+ if (!xnCorePath) return '';
+ return path.join(xnCorePath, 'Project', 'Service');
+}
+
// 服务文件目录路径
function getServicePath() {
const xnCorePath = getXNCorePath();
@@ -78,39 +78,14 @@ function getServicePath() {
}
// 日志目录路径处理
-async function getActualLogPath(requestedPath) {
- const xnCorePath = getXNCorePath();
+async function getActualLogPath() {
const currentDir = process.cwd();
+ const logPath = path.join(currentDir, 'log');
- // 如果请求的路径是/log开头
- if (requestedPath.startsWith('/log')) {
- // 首先尝试在XNCore下查找
- const xnCoreLogPath = path.join(xnCorePath, requestedPath);
- if (fs.existsSync(xnCoreLogPath)) {
- return xnCoreLogPath;
- }
-
- // 如果XNCore下不存在,则尝试在当前目录下查找
- const currentLogPath = path.join(currentDir, requestedPath);
- if (fs.existsSync(currentLogPath)) {
- return currentLogPath;
- }
-
- // 如果都不存在,默认使用XNCore下的路径
- return xnCoreLogPath;
- }
- // 如果已经是绝对路径并且在XNCore下
- else if (requestedPath.startsWith(xnCorePath)) {
- return requestedPath;
- }
- // 如果已经是绝对路径并且在当前目录下
- else if (requestedPath.startsWith(currentDir)) {
- return requestedPath;
- }
- // 默认不允许访问其他路径
- else {
- return null;
- }
+ // 确保日志目录存在
+ await ensureDirectoryExists(logPath);
+
+ return logPath;
}
// 检查文件路径安全性
@@ -147,29 +122,14 @@ async function ensureDirectoryExists(dirPath) {
return false;
}
-// XML 验证
-async function validateXml(content) {
- if (!content || !content.trim()) return true;
-
- try {
- const parser = new (require('xml2js')).Parser();
- await parser.parseStringPromise(content);
- return true;
- } catch (xmlError) {
- return { error: 'XML格式无效', details: xmlError.message };
- }
-}
-
module.exports = {
getXNCorePath,
- getScenarioPath,
getModelPath,
getProjectModelPath,
getServicePath,
getActualLogPath,
isPathSafe,
ensureDirectoryExists,
- validateXml,
getDBConnection,
closeDBConnection
};
\ No newline at end of file