feat: add delete options for proxy nodes and history records
This commit is contained in:
+50
-5
@@ -765,11 +765,12 @@
|
|||||||
<th>节点数量</th>
|
<th>节点数量</th>
|
||||||
<th>本地下载</th>
|
<th>本地下载</th>
|
||||||
<th>一键导入</th>
|
<th>一键导入</th>
|
||||||
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="history-list">
|
<tbody id="history-list">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
|
<td colspan="5" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
|
||||||
暂无生成历史记录,运行生成配置后将在此归档
|
暂无生成历史记录,运行生成配置后将在此归档
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1119,9 +1120,14 @@
|
|||||||
<td style="font-family: var(--font-mono); font-size: 0.85rem;">${node.server}</td>
|
<td style="font-family: var(--font-mono); font-size: 0.85rem;">${node.server}</td>
|
||||||
<td style="font-family: var(--font-mono); font-size: 0.85rem;">${node.port || node.server_port}</td>
|
<td style="font-family: var(--font-mono); font-size: 0.85rem;">${node.port || node.server_port}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;" onclick="openNodeDrawer(${idx})">
|
<div style="display: flex; gap: 0.5rem;">
|
||||||
<i data-lucide="sliders" style="width: 12px; height: 12px;"></i>修改参数
|
<button class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;" onclick="openNodeDrawer(${idx})">
|
||||||
</button>
|
<i data-lucide="sliders" style="width: 12px; height: 12px;"></i>修改参数
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;" onclick="deleteProxy(${idx})">
|
||||||
|
<i data-lucide="trash-2" style="width: 12px; height: 12px;"></i>删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
list.appendChild(tr);
|
list.appendChild(tr);
|
||||||
@@ -1129,6 +1135,16 @@
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteProxy(idx) {
|
||||||
|
const nodeName = proxies[idx].name || proxies[idx].tag;
|
||||||
|
if (confirm(`确定要删除代理节点 "${nodeName}" 吗?\n(注:这仅会从当前页面内存中删除。如需应用更改,请在删除后点击右上角“保存并生成配置”按钮重新生成配置文件)`)) {
|
||||||
|
log(`在内存中删除代理节点: ${nodeName}`);
|
||||||
|
proxies.splice(idx, 1);
|
||||||
|
renderProxiesTable();
|
||||||
|
showToast('代理节点已删除');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Modal Drawer for Editing Nodes
|
// Modal Drawer for Editing Nodes
|
||||||
let currentEditNodeIndex = null;
|
let currentEditNodeIndex = null;
|
||||||
|
|
||||||
@@ -1362,6 +1378,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteHistory(yamlFile, jsonFile, timeStr) {
|
||||||
|
if (confirm(`确定要删除该历史生成记录吗?\n生成时间: ${timeStr}\n(注意:此操作将物理删除对应的配置文件归档且不可逆!)`)) {
|
||||||
|
log(`正在请求删除历史记录: ${timeStr}`);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/history/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ yamlFile, jsonFile })
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
log(`成功删除历史记录: ${timeStr}`, 'success');
|
||||||
|
showToast('历史记录删除成功');
|
||||||
|
loadHistory();
|
||||||
|
} else {
|
||||||
|
const errText = await res.text();
|
||||||
|
log(`删除历史记录失败: ${errText}`, 'error');
|
||||||
|
showToast('删除历史记录失败', 'error');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(`删除历史记录异常: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderHistoryList(list) {
|
function renderHistoryList(list) {
|
||||||
const tbody = document.getElementById('history-list');
|
const tbody = document.getElementById('history-list');
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
@@ -1369,7 +1409,7 @@
|
|||||||
if (!list || list.length === 0) {
|
if (!list || list.length === 0) {
|
||||||
tbody.innerHTML = `
|
tbody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
|
<td colspan="5" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
|
||||||
暂无生成历史记录,运行生成配置后将在此归档
|
暂无生成历史记录,运行生成配置后将在此归档
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1411,6 +1451,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-danger" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;" onclick="deleteHistory('${item.yamlFile}', '${item.jsonFile}', '${item.timeStr}')">
|
||||||
|
<i data-lucide="trash-2" style="width: 12px; height: 12px;"></i>删除
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -733,6 +733,60 @@ function startServer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API: POST /api/history/delete
|
||||||
|
if (pathname === '/api/history/delete' && req.method === 'POST') {
|
||||||
|
try {
|
||||||
|
const { yamlFile, jsonFile } = await readJsonBody(req);
|
||||||
|
if (!yamlFile || !jsonFile) {
|
||||||
|
res.writeHead(400);
|
||||||
|
res.end('Missing yamlFile or jsonFile parameter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security check: prevent directory traversal
|
||||||
|
const safeRegex = /^CatMata-sub-\d{8}-\d{6}\.(yaml|json)$/;
|
||||||
|
if (!safeRegex.test(yamlFile) || !safeRegex.test(jsonFile)) {
|
||||||
|
res.writeHead(400);
|
||||||
|
res.end('Invalid file name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyDir = path.join(outDir, 'history');
|
||||||
|
const yamlPath = path.join(historyDir, yamlFile);
|
||||||
|
const jsonPath = path.join(historyDir, jsonFile);
|
||||||
|
|
||||||
|
// Delete physical files
|
||||||
|
if (fs.existsSync(yamlPath)) {
|
||||||
|
fs.unlinkSync(yamlPath);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(jsonPath)) {
|
||||||
|
fs.unlinkSync(jsonPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update history.json
|
||||||
|
const historyJsonPath = path.join(historyDir, 'history.json');
|
||||||
|
let historyList = [];
|
||||||
|
if (fs.existsSync(historyJsonPath)) {
|
||||||
|
try {
|
||||||
|
historyList = JSON.parse(fs.readFileSync(historyJsonPath, 'utf-8'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse history.json, resetting:", e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
historyList = historyList.filter(item => item.yamlFile !== yamlFile || item.jsonFile !== jsonFile);
|
||||||
|
|
||||||
|
fs.writeFileSync(historyJsonPath, JSON.stringify(historyList, null, 2), 'utf-8');
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ success: true }));
|
||||||
|
} catch (e) {
|
||||||
|
res.writeHead(500);
|
||||||
|
res.end(e.message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.writeHead(404);
|
res.writeHead(404);
|
||||||
res.end('Not Found');
|
res.end('Not Found');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user