mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-04 01:06:36 +08:00
feat: add per-IP limit controls
This commit is contained in:
@@ -71,6 +71,9 @@ export interface ForwardApiItem {
|
|||||||
userId?: number;
|
userId?: number;
|
||||||
tunnelId?: number;
|
tunnelId?: number;
|
||||||
speedId?: number | null;
|
speedId?: number | null;
|
||||||
|
ipMaxConn?: number;
|
||||||
|
ipSpeedId?: number | null;
|
||||||
|
ipSpeedLimitName?: string;
|
||||||
maxConn?: number;
|
maxConn?: number;
|
||||||
proxyProtocol?: number;
|
proxyProtocol?: number;
|
||||||
inx?: number;
|
inx?: number;
|
||||||
@@ -372,6 +375,8 @@ export interface ForwardMutationPayload {
|
|||||||
remoteAddr?: string;
|
remoteAddr?: string;
|
||||||
strategy?: string;
|
strategy?: string;
|
||||||
speedId?: number | null;
|
speedId?: number | null;
|
||||||
|
ipMaxConn?: number;
|
||||||
|
ipSpeedId?: number | null;
|
||||||
maxConn?: number;
|
maxConn?: number;
|
||||||
proxyProtocol?: number;
|
proxyProtocol?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,9 @@ interface Forward {
|
|||||||
userId?: number;
|
userId?: number;
|
||||||
inx?: number;
|
inx?: number;
|
||||||
speedId?: number | null;
|
speedId?: number | null;
|
||||||
|
ipMaxConn?: number;
|
||||||
|
ipSpeedId?: number | null;
|
||||||
|
ipSpeedLimitName?: string;
|
||||||
proxyProtocol?: number;
|
proxyProtocol?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +163,8 @@ interface ForwardForm {
|
|||||||
interfaceName?: string;
|
interfaceName?: string;
|
||||||
strategy: string;
|
strategy: string;
|
||||||
speedId: number | null;
|
speedId: number | null;
|
||||||
|
ipMaxConn?: number;
|
||||||
|
ipSpeedId: number | null;
|
||||||
maxConn?: number;
|
maxConn?: number;
|
||||||
proxyProtocol?: number;
|
proxyProtocol?: number;
|
||||||
}
|
}
|
||||||
@@ -578,6 +583,16 @@ const mapForwardApiItems = (items: ForwardApiItem[]): Forward[] => {
|
|||||||
typeof forward.speedId === "number" || forward.speedId === null
|
typeof forward.speedId === "number" || forward.speedId === null
|
||||||
? forward.speedId
|
? forward.speedId
|
||||||
: undefined,
|
: 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,
|
maxConn: typeof forward.maxConn === "number" ? forward.maxConn : undefined,
|
||||||
proxyProtocol:
|
proxyProtocol:
|
||||||
typeof forward.proxyProtocol === "number"
|
typeof forward.proxyProtocol === "number"
|
||||||
@@ -1316,6 +1331,8 @@ export default function ForwardPage() {
|
|||||||
interfaceName: "",
|
interfaceName: "",
|
||||||
strategy: "fifo",
|
strategy: "fifo",
|
||||||
speedId: null,
|
speedId: null,
|
||||||
|
ipMaxConn: 0,
|
||||||
|
ipSpeedId: null,
|
||||||
maxConn: 0,
|
maxConn: 0,
|
||||||
proxyProtocol: 0,
|
proxyProtocol: 0,
|
||||||
});
|
});
|
||||||
@@ -2030,6 +2047,7 @@ export default function ForwardPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectedSpeedId = normalizeSpeedId(form.speedId);
|
const selectedSpeedId = normalizeSpeedId(form.speedId);
|
||||||
|
const selectedIPSpeedId = normalizeSpeedId(form.ipSpeedId);
|
||||||
|
|
||||||
const validateForm = (): boolean => {
|
const validateForm = (): boolean => {
|
||||||
const newErrors: { [key: string]: string } = {};
|
const newErrors: { [key: string]: string } = {};
|
||||||
@@ -2105,6 +2123,8 @@ export default function ForwardPage() {
|
|||||||
interfaceName: "",
|
interfaceName: "",
|
||||||
strategy: "fifo",
|
strategy: "fifo",
|
||||||
speedId: null,
|
speedId: null,
|
||||||
|
ipMaxConn: 0,
|
||||||
|
ipSpeedId: null,
|
||||||
proxyProtocol: 0,
|
proxyProtocol: 0,
|
||||||
});
|
});
|
||||||
setErrors({});
|
setErrors({});
|
||||||
@@ -2126,6 +2146,8 @@ export default function ForwardPage() {
|
|||||||
interfaceName: forward.interfaceName || "",
|
interfaceName: forward.interfaceName || "",
|
||||||
strategy: forward.strategy || "fifo",
|
strategy: forward.strategy || "fifo",
|
||||||
speedId: normalizeSpeedId(forward.speedId),
|
speedId: normalizeSpeedId(forward.speedId),
|
||||||
|
ipMaxConn: forward.ipMaxConn ?? 0,
|
||||||
|
ipSpeedId: normalizeSpeedId(forward.ipSpeedId),
|
||||||
maxConn: forward.maxConn ?? 0,
|
maxConn: forward.maxConn ?? 0,
|
||||||
proxyProtocol: forward.proxyProtocol ?? 0,
|
proxyProtocol: forward.proxyProtocol ?? 0,
|
||||||
});
|
});
|
||||||
@@ -2245,6 +2267,8 @@ export default function ForwardPage() {
|
|||||||
let res: { code: number; msg: string };
|
let res: { code: number; msg: string };
|
||||||
const normalizedSpeedId = normalizeSpeedId(form.speedId);
|
const normalizedSpeedId = normalizeSpeedId(form.speedId);
|
||||||
const speedLimitAutoCleared = isMissingSpeedLimit(form.speedId);
|
const speedLimitAutoCleared = isMissingSpeedLimit(form.speedId);
|
||||||
|
const normalizedIPSpeedId = normalizeSpeedId(form.ipSpeedId);
|
||||||
|
const ipSpeedLimitAutoCleared = isMissingSpeedLimit(form.ipSpeedId);
|
||||||
|
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
const updateData = {
|
const updateData = {
|
||||||
@@ -2256,6 +2280,8 @@ export default function ForwardPage() {
|
|||||||
remoteAddr: processedRemoteAddr,
|
remoteAddr: processedRemoteAddr,
|
||||||
strategy: addressCount > 1 ? form.strategy : "fifo",
|
strategy: addressCount > 1 ? form.strategy : "fifo",
|
||||||
speedId: normalizedSpeedId,
|
speedId: normalizedSpeedId,
|
||||||
|
ipMaxConn: form.ipMaxConn,
|
||||||
|
ipSpeedId: normalizedIPSpeedId,
|
||||||
maxConn: form.maxConn,
|
maxConn: form.maxConn,
|
||||||
proxyProtocol: form.proxyProtocol,
|
proxyProtocol: form.proxyProtocol,
|
||||||
};
|
};
|
||||||
@@ -2270,6 +2296,8 @@ export default function ForwardPage() {
|
|||||||
remoteAddr: processedRemoteAddr,
|
remoteAddr: processedRemoteAddr,
|
||||||
strategy: addressCount > 1 ? form.strategy : "fifo",
|
strategy: addressCount > 1 ? form.strategy : "fifo",
|
||||||
speedId: normalizedSpeedId,
|
speedId: normalizedSpeedId,
|
||||||
|
ipMaxConn: form.ipMaxConn,
|
||||||
|
ipSpeedId: normalizedIPSpeedId,
|
||||||
maxConn: form.maxConn,
|
maxConn: form.maxConn,
|
||||||
proxyProtocol: form.proxyProtocol,
|
proxyProtocol: form.proxyProtocol,
|
||||||
};
|
};
|
||||||
@@ -2297,6 +2325,12 @@ export default function ForwardPage() {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (ipSpeedLimitAutoCleared) {
|
||||||
|
toast("所选每 IP 限速规则不存在,已自动清除为不限速", {
|
||||||
|
icon: "⚠️",
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
toast.success(isEdit ? "修改成功" : "创建成功");
|
toast.success(isEdit ? "修改成功" : "创建成功");
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
await refreshForwardList(false);
|
await refreshForwardList(false);
|
||||||
@@ -4911,6 +4945,27 @@ export default function ForwardPage() {
|
|||||||
setForm((prev) => ({ ...prev, maxConn: value }));
|
setForm((prev) => ({ ...prev, maxConn: value }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
description="每个客户端 IP 可同时建立的最大连接数;0 或空表示不限制。"
|
||||||
|
label="每 IP 最大连接数"
|
||||||
|
min="0"
|
||||||
|
placeholder="0 或空表示不限制"
|
||||||
|
type="number"
|
||||||
|
value={
|
||||||
|
form.ipMaxConn === 0
|
||||||
|
? ""
|
||||||
|
: String(form.ipMaxConn || "")
|
||||||
|
}
|
||||||
|
variant="bordered"
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = Math.max(
|
||||||
|
Number(e.target.value) || 0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
setForm((prev) => ({ ...prev, ipMaxConn: value }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Select
|
<Select
|
||||||
description="启用 PROXY protocol,用于透传客户端真实 IP"
|
description="启用 PROXY protocol,用于透传客户端真实 IP"
|
||||||
label="Proxy Protocol"
|
label="Proxy Protocol"
|
||||||
@@ -4963,6 +5018,40 @@ export default function ForwardPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
{isAdmin && (
|
||||||
|
<Select
|
||||||
|
description="每个客户端 IP 独享该限速规则;不选择表示不限制。"
|
||||||
|
label="每 IP 限速"
|
||||||
|
placeholder="不限速"
|
||||||
|
selectedKeys={
|
||||||
|
selectedIPSpeedId !== null
|
||||||
|
? [selectedIPSpeedId.toString()]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
variant="bordered"
|
||||||
|
onSelectionChange={(keys) => {
|
||||||
|
const selectedKey = Array.from(keys)[0] as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
ipSpeedId: selectedKey
|
||||||
|
? Number(selectedKey)
|
||||||
|
: null,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{availableSpeedLimits.map((speedLimit) => (
|
||||||
|
<SelectItem
|
||||||
|
key={speedLimit.id.toString()}
|
||||||
|
textValue={speedLimit.name}
|
||||||
|
>
|
||||||
|
{speedLimit.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
Reference in New Issue
Block a user