From 8f28ae3c67090b5f2b49605b39bef935e87acf3e Mon Sep 17 00:00:00 2001 From: jocayn <1579649885@qq.com> Date: Sat, 13 Jun 2026 15:53:09 +0800 Subject: [PATCH] feat: add delete options for proxy nodes and history records --- public/index.html | 55 ++++++++++++++++++++++++++++++++++++++++++----- sub2proxy.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/public/index.html b/public/index.html index d6ee741..7072d30 100644 --- a/public/index.html +++ b/public/index.html @@ -765,11 +765,12 @@ 节点数量 本地下载 一键导入 + 操作 - + 暂无生成历史记录,运行生成配置后将在此归档 @@ -1119,9 +1120,14 @@ ${node.server} ${node.port || node.server_port} - +
+ + +
`; list.appendChild(tr); @@ -1129,6 +1135,16 @@ 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 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) { const tbody = document.getElementById('history-list'); tbody.innerHTML = ''; @@ -1369,7 +1409,7 @@ if (!list || list.length === 0) { tbody.innerHTML = ` - + 暂无生成历史记录,运行生成配置后将在此归档 @@ -1411,6 +1451,11 @@ + + + `; tbody.appendChild(tr); }); diff --git a/sub2proxy.js b/sub2proxy.js index 48e6841..c4e18bb 100644 --- a/sub2proxy.js +++ b/sub2proxy.js @@ -733,6 +733,60 @@ function startServer() { 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.end('Not Found'); });