V0.21.13.250611_alpha:修复了一些后端及图标bug
This commit is contained in:
parent
455f4ca783
commit
a3dc807854
Binary file not shown.
@ -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;
|
||||
}
|
||||
</style>
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
@ -1123,6 +1312,44 @@ class DataMonitor extends HTMLElement {
|
||||
<span id="statusText">未监控</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<button class="csv-inject-button" id="csvInjectButton">
|
||||
<img src="assets/icons/png/file.png" alt="CSV" style="width: 16px; height: 16px;">
|
||||
CSV文件注入
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-overlay" id="csvModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">CSV文件注入</h3>
|
||||
<button class="modal-close" id="modalClose">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>选择CSV文件</label>
|
||||
<div class="file-input-wrapper">
|
||||
<div class="file-input-trigger" id="fileInputTrigger">
|
||||
点击或拖拽文件到此处
|
||||
</div>
|
||||
<input type="file" id="csvFileInput" accept=".csv" />
|
||||
</div>
|
||||
<div class="file-name" id="fileName"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>注入频率 (Hz)</label>
|
||||
<input type="number" id="injectFrequency" min="10" max="10000" step="10" value="100" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>注入时长 (秒)</label>
|
||||
<input type="number" id="injectDuration" min="1" max="3600" step="1" value="60" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="modal-button secondary" id="modalCancel">取消</button>
|
||||
<button class="modal-button primary" id="modalConfirm">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="monitor-container">
|
||||
<div class="tree-container">
|
||||
@ -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;
|
||||
// 清理所有图表窗口
|
||||
|
@ -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}`);
|
||||
|
@ -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';
|
||||
|
@ -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');
|
||||
|
@ -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;
|
@ -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;
|
@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scenario>
|
||||
<Environment
|
||||
OSName="Debian"
|
||||
Version="11"
|
||||
RTXVersion="preempt-rt"
|
||||
CPUAffinity="0,1"
|
||||
BaseFrequency="120"
|
||||
WorkPath="/home/jin/Myprj/XNSim/Release/"
|
||||
ModelsPath="Models/"
|
||||
ServicesPath="Services/"
|
||||
DomainID="10"
|
||||
/>
|
||||
<ConsoleOutput Debug="1" Info="1" Error="1" Warning="1" />
|
||||
<Log Debug="0" Info="1" Error="1" Warning="1" />
|
||||
<ModelGroup Name="新模型组" FreqGroup="0" Priority="99" CPUAff="0">
|
||||
<!-- 这里添加模型 -->
|
||||
</ModelGroup>
|
||||
<ServicesList>
|
||||
<!-- 这里添加服务 -->
|
||||
</ServicesList>
|
||||
</Scenario>`;
|
||||
|
||||
// 写入文件内容
|
||||
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;
|
@ -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);
|
||||
|
@ -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
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user