diff --git a/.sisyphus/ralph-loop.local.md b/.sisyphus/ralph-loop.local.md deleted file mode 100644 index 506653a..0000000 --- a/.sisyphus/ralph-loop.local.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -active: true -iteration: 1 -max_iterations: 100 -completion_promise: "DONE" -started_at: "2026-01-24T05:20:50.887Z" -session_id: "ses_41192b7d7ffewFHxf1dDS7ESHO" ---- -Complete the task as instructed diff --git a/springboot-backend/src/main/java/com/admin/controller/ForwardController.java b/springboot-backend/src/main/java/com/admin/controller/ForwardController.java index 58591e8..b8c55c3 100644 --- a/springboot-backend/src/main/java/com/admin/controller/ForwardController.java +++ b/springboot-backend/src/main/java/com/admin/controller/ForwardController.java @@ -106,6 +106,18 @@ public class ForwardController extends BaseController { return forwardService.batchDeleteForwards(batchDeleteDto); } + @LogAnnotation + @PostMapping("/batch-pause") + public R batchPause(@Validated @RequestBody BatchDeleteDto batchDeleteDto) { + return forwardService.batchPauseForwards(batchDeleteDto); + } + + @LogAnnotation + @PostMapping("/batch-resume") + public R batchResume(@Validated @RequestBody BatchDeleteDto batchDeleteDto) { + return forwardService.batchResumeForwards(batchDeleteDto); + } + @LogAnnotation @PostMapping("/batch-redeploy") public R batchRedeploy(@Validated @RequestBody BatchRedeployDto batchRedeployDto) { diff --git a/springboot-backend/src/main/java/com/admin/service/ForwardService.java b/springboot-backend/src/main/java/com/admin/service/ForwardService.java index f942f26..be53884 100644 --- a/springboot-backend/src/main/java/com/admin/service/ForwardService.java +++ b/springboot-backend/src/main/java/com/admin/service/ForwardService.java @@ -85,6 +85,10 @@ public interface ForwardService extends IService { R updateForwardOrder(Map params); R batchDeleteForwards(BatchDeleteDto batchDeleteDto); + + R batchPauseForwards(BatchDeleteDto batchDeleteDto); + + R batchResumeForwards(BatchDeleteDto batchDeleteDto); R batchRedeployForwards(BatchRedeployDto batchRedeployDto); diff --git a/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java index d57426c..21d8c14 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/ForwardServiceImpl.java @@ -1304,6 +1304,37 @@ public class ForwardServiceImpl extends ServiceImpl impl return R.ok(result); } + @Override + @Transactional + public R batchPauseForwards(BatchDeleteDto batchDeleteDto) { + return batchChangeForwardStatus(batchDeleteDto.getIds(), 0, "PauseService"); + } + + @Override + @Transactional + public R batchResumeForwards(BatchDeleteDto batchDeleteDto) { + return batchChangeForwardStatus(batchDeleteDto.getIds(), 1, "ResumeService"); + } + + private R batchChangeForwardStatus(List ids, int targetStatus, String gostMethod) { + BatchOperationResultDto result = new BatchOperationResultDto(); + + for (Long id : ids) { + try { + R changeResult = changeForwardStatus(id, targetStatus, gostMethod); + if (changeResult.getCode() == 0) { + result.incrementSuccess(); + } else { + result.addFailedItem(id, changeResult.getMsg()); + } + } catch (Exception e) { + result.addFailedItem(id, e.getMessage()); + } + } + + return R.ok(result); + } + @Override @Transactional public R batchRedeployForwards(BatchRedeployDto batchRedeployDto) { @@ -1384,6 +1415,15 @@ public class ForwardServiceImpl extends ServiceImpl impl result.addFailedItem(forwardId, "已是目标隧道"); continue; } + + List existingForwardPorts = forwardPortService.list( + new QueryWrapper().eq("forward_id", forwardId).orderByAsc("id") + ); + Integer originalInPort = existingForwardPorts.stream() + .map(ForwardPort::getPort) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); Tunnel oldTunnel = validateTunnel(forward.getTunnelId()); if (oldTunnel != null) { @@ -1424,7 +1464,7 @@ public class ForwardServiceImpl extends ServiceImpl impl List newChainTunnels = chainTunnelService.list( new QueryWrapper().eq("tunnel_id", targetTunnel.getId()).eq("chain_type", 1) ); - List chainTunnelsWithPort = get_port(newChainTunnels, null, forwardId); + List chainTunnelsWithPort = allocatePortsForBatchTunnelChange(newChainTunnels, originalInPort, forwardId); for (ChainTunnel chainTunnel : chainTunnelsWithPort) { ForwardPort forwardPort = new ForwardPort(); @@ -1450,4 +1490,23 @@ public class ForwardServiceImpl extends ServiceImpl impl return R.ok(result); } + private List allocatePortsForBatchTunnelChange(List newChainTunnels, Integer originalInPort, Long forwardId) { + if (originalInPort == null) { + return get_port(newChainTunnels, null, forwardId); + } + + try { + return get_port(newChainTunnels, originalInPort, forwardId); + } catch (RuntimeException originalPortError) { + try { + return get_port(newChainTunnels, null, forwardId); + } catch (RuntimeException autoAllocateError) { + throw new RuntimeException( + "原入口端口 " + originalInPort + " 在目标隧道不可用,自动分配新端口也失败: " + autoAllocateError.getMessage(), + autoAllocateError + ); + } + } + } + } diff --git a/vite-frontend/package.json b/vite-frontend/package.json index d89bafe..c4016d1 100644 --- a/vite-frontend/package.json +++ b/vite-frontend/package.json @@ -55,7 +55,7 @@ "react-dom": "18.3.1", "react-hot-toast": "^2.5.2", "react-is": "^19.2.4", - "react-router-dom": "6.23.0", + "react-router-dom": "6.30.3", "recharts": "^3.1.1", "sonner": "^2.0.6", "tailwind-variants": "1.0.0", @@ -64,14 +64,14 @@ "devDependencies": { "@eslint/compat": "1.2.8", "@eslint/eslintrc": "3.3.1", - "@eslint/js": "9.25.1", + "@eslint/js": "9.39.2", "@types/node": "^24.3.0", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", "@vitejs/plugin-react": "4.4.1", - "eslint": "9.25.1", + "eslint": "9.39.2", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-jsx-a11y": "6.10.2", @@ -84,7 +84,7 @@ "postcss": "8.5.6", "prettier": "3.5.3", "typescript": "5.6.3", - "vite": "5.4.11", + "vite": "5.4.21", "vite-tsconfig-paths": "4.3.2" } } diff --git a/vite-frontend/src/api/index.ts b/vite-frontend/src/api/index.ts index d943379..5b3164f 100644 --- a/vite-frontend/src/api/index.ts +++ b/vite-frontend/src/api/index.ts @@ -131,6 +131,10 @@ export const verifyCaptcha = (data: { captchaId: string; trackData: string }) => // 批量操作接口 export const batchDeleteForwards = (ids: number[]) => Network.post("/forward/batch-delete", { ids }); +export const batchPauseForwards = (ids: number[]) => + Network.post("/forward/batch-pause", { ids }); +export const batchResumeForwards = (ids: number[]) => + Network.post("/forward/batch-resume", { ids }); export const batchDeleteTunnels = (ids: number[]) => Network.post("/tunnel/batch-delete", { ids }); export const batchDeleteNodes = (ids: number[]) => diff --git a/vite-frontend/src/layouts/admin.tsx b/vite-frontend/src/layouts/admin.tsx index 2bf18e8..650e5bb 100644 --- a/vite-frontend/src/layouts/admin.tsx +++ b/vite-frontend/src/layouts/admin.tsx @@ -62,7 +62,7 @@ export default function AdminLayout({ const menuItems: MenuItem[] = [ { path: "/dashboard", - label: "仪表板", + label: "仪表", icon: ( @@ -71,7 +71,7 @@ export default function AdminLayout({ }, { path: "/forward", - label: "转发管理", + label: "转发", icon: ( @@ -136,7 +136,7 @@ export default function AdminLayout({ }, { path: "/config", - label: "网站配置", + label: "设置", icon: ( { + if (selectedIds.size === 0) return; + setBatchLoading(true); + try { + const ids = Array.from(selectedIds); + const res = enable + ? await batchResumeForwards(ids) + : await batchPauseForwards(ids); + if (res.code === 0) { + const result = res.data; + if (result.failCount === 0) { + toast.success( + enable + ? `成功启用 ${result.successCount} 项` + : `成功停用 ${result.successCount} 项`, + ); + } else { + toast.error(`成功 ${result.successCount} 项,失败 ${result.failCount} 项`); + } + setSelectedIds(new Set()); + setSelectMode(false); + loadData(false); + } else { + toast.error(res.msg || (enable ? "启用失败" : "停用失败")); + } + } catch (e: any) { + toast.error(e.message || (enable ? "启用失败" : "停用失败")); } finally { setBatchLoading(false); } @@ -1340,10 +1374,10 @@ export default function ForwardPage() { setSelectMode(false); loadData(false); } else { - toast.error(res.msg || "批量重新下发失败"); + toast.error(res.msg || "下发失败"); } } catch (e: any) { - toast.error(e.message || "批量重新下发失败"); + toast.error(e.message || "下发失败"); } finally { setBatchLoading(false); } @@ -1370,10 +1404,10 @@ export default function ForwardPage() { setBatchTargetTunnelId(null); loadData(false); } else { - toast.error(res.msg || "批量换隧道失败"); + toast.error(res.msg || "隧道失败"); } } catch (e: any) { - toast.error(e.message || "批量换隧道失败"); + toast.error(e.message || "隧道失败"); } finally { setBatchLoading(false); } @@ -1731,9 +1765,9 @@ export default function ForwardPage() { return (
{/* 页面头部 */} -
+
-
+
{/* 显示模式切换按钮 */}
{selectMode && selectedIds.size > 0 && ( -
- 已选择 {selectedIds.size} 项 - - - - - +
+
+ 已选择 {selectedIds.size} 项 + + + + + + + +
)} @@ -3069,7 +3123,7 @@ export default function ForwardPage() { {(onClose) => ( <> - 确认批量删除 + 确认删除

确定要删除选中的 {selectedIds.size} 项转发吗?此操作不可撤销。

@@ -3095,7 +3149,7 @@ export default function ForwardPage() { {(onClose) => ( <> - 批量换隧道 + 隧道

将选中的 {selectedIds.size} 项转发迁移到新隧道: