From a5a500bc0fac9ab6b7fe2fb2ffddeef5b0685b80 Mon Sep 17 00:00:00 2001 From: sagitchu Date: Mon, 27 Apr 2026 23:17:02 +0800 Subject: [PATCH] feat: add per-IP limit controls --- vite-frontend/src/api/types.ts | 5 ++ vite-frontend/src/pages/forward.tsx | 89 +++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/vite-frontend/src/api/types.ts b/vite-frontend/src/api/types.ts index cd3c5f0..1ff16cc 100644 --- a/vite-frontend/src/api/types.ts +++ b/vite-frontend/src/api/types.ts @@ -71,6 +71,9 @@ export interface ForwardApiItem { userId?: number; tunnelId?: number; speedId?: number | null; + ipMaxConn?: number; + ipSpeedId?: number | null; + ipSpeedLimitName?: string; maxConn?: number; proxyProtocol?: number; inx?: number; @@ -372,6 +375,8 @@ export interface ForwardMutationPayload { remoteAddr?: string; strategy?: string; speedId?: number | null; + ipMaxConn?: number; + ipSpeedId?: number | null; maxConn?: number; proxyProtocol?: number; } diff --git a/vite-frontend/src/pages/forward.tsx b/vite-frontend/src/pages/forward.tsx index 2e86b78..3d23627 100644 --- a/vite-frontend/src/pages/forward.tsx +++ b/vite-frontend/src/pages/forward.tsx @@ -125,6 +125,9 @@ interface Forward { userId?: number; inx?: number; speedId?: number | null; + ipMaxConn?: number; + ipSpeedId?: number | null; + ipSpeedLimitName?: string; proxyProtocol?: number; } @@ -160,6 +163,8 @@ interface ForwardForm { interfaceName?: string; strategy: string; speedId: number | null; + ipMaxConn?: number; + ipSpeedId: number | null; maxConn?: number; proxyProtocol?: number; } @@ -578,6 +583,16 @@ const mapForwardApiItems = (items: ForwardApiItem[]): Forward[] => { typeof forward.speedId === "number" || forward.speedId === null ? forward.speedId : undefined, + ipMaxConn: + typeof forward.ipMaxConn === "number" ? forward.ipMaxConn : undefined, + ipSpeedId: + typeof forward.ipSpeedId === "number" || forward.ipSpeedId === null + ? forward.ipSpeedId + : undefined, + ipSpeedLimitName: + typeof forward.ipSpeedLimitName === "string" + ? forward.ipSpeedLimitName + : undefined, maxConn: typeof forward.maxConn === "number" ? forward.maxConn : undefined, proxyProtocol: typeof forward.proxyProtocol === "number" @@ -1316,6 +1331,8 @@ export default function ForwardPage() { interfaceName: "", strategy: "fifo", speedId: null, + ipMaxConn: 0, + ipSpeedId: null, maxConn: 0, proxyProtocol: 0, }); @@ -2030,6 +2047,7 @@ export default function ForwardPage() { }; const selectedSpeedId = normalizeSpeedId(form.speedId); + const selectedIPSpeedId = normalizeSpeedId(form.ipSpeedId); const validateForm = (): boolean => { const newErrors: { [key: string]: string } = {}; @@ -2105,6 +2123,8 @@ export default function ForwardPage() { interfaceName: "", strategy: "fifo", speedId: null, + ipMaxConn: 0, + ipSpeedId: null, proxyProtocol: 0, }); setErrors({}); @@ -2126,6 +2146,8 @@ export default function ForwardPage() { interfaceName: forward.interfaceName || "", strategy: forward.strategy || "fifo", speedId: normalizeSpeedId(forward.speedId), + ipMaxConn: forward.ipMaxConn ?? 0, + ipSpeedId: normalizeSpeedId(forward.ipSpeedId), maxConn: forward.maxConn ?? 0, proxyProtocol: forward.proxyProtocol ?? 0, }); @@ -2245,6 +2267,8 @@ export default function ForwardPage() { let res: { code: number; msg: string }; const normalizedSpeedId = normalizeSpeedId(form.speedId); const speedLimitAutoCleared = isMissingSpeedLimit(form.speedId); + const normalizedIPSpeedId = normalizeSpeedId(form.ipSpeedId); + const ipSpeedLimitAutoCleared = isMissingSpeedLimit(form.ipSpeedId); if (isEdit) { const updateData = { @@ -2256,6 +2280,8 @@ export default function ForwardPage() { remoteAddr: processedRemoteAddr, strategy: addressCount > 1 ? form.strategy : "fifo", speedId: normalizedSpeedId, + ipMaxConn: form.ipMaxConn, + ipSpeedId: normalizedIPSpeedId, maxConn: form.maxConn, proxyProtocol: form.proxyProtocol, }; @@ -2270,6 +2296,8 @@ export default function ForwardPage() { remoteAddr: processedRemoteAddr, strategy: addressCount > 1 ? form.strategy : "fifo", speedId: normalizedSpeedId, + ipMaxConn: form.ipMaxConn, + ipSpeedId: normalizedIPSpeedId, maxConn: form.maxConn, proxyProtocol: form.proxyProtocol, }; @@ -2297,6 +2325,12 @@ export default function ForwardPage() { duration: 5000, }); } + if (ipSpeedLimitAutoCleared) { + toast("所选每 IP 限速规则不存在,已自动清除为不限速", { + icon: "⚠️", + duration: 5000, + }); + } toast.success(isEdit ? "修改成功" : "创建成功"); setModalOpen(false); await refreshForwardList(false); @@ -4911,6 +4945,27 @@ export default function ForwardPage() { setForm((prev) => ({ ...prev, maxConn: value })); }} /> + { + const value = Math.max( + Number(e.target.value) || 0, + 0, + ); + + setForm((prev) => ({ ...prev, ipMaxConn: value })); + }} + /> { + const selectedKey = Array.from(keys)[0] as + | string + | undefined; + + setForm((prev) => ({ + ...prev, + ipSpeedId: selectedKey + ? Number(selectedKey) + : null, + })); + }} + > + {availableSpeedLimits.map((speedLimit) => ( + + {speedLimit.name} + + ))} + + )}