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');
});