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; + }
@@ -1123,6 +1312,44 @@ class DataMonitor extends HTMLElement { 未监控
+
+ +
+ +
@@ -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