refactor: 统一术语,将'转发'改为'规则' (#273)

- 更新所有页面中的'转发'术语为'规则'
- 统一UI文案,提升用户体验一致性
- 关联 #269 #271

Entire-Checkpoint: 5646e42aa33b
This commit is contained in:
sagit
2026-03-05 15:07:42 +08:00
committed by GitHub
parent f45b580984
commit f6eb88d75e
6 changed files with 367 additions and 330 deletions
+3 -3
View File
@@ -119,8 +119,8 @@ const CONFIG_ITEMS: ConfigItem[] = [
}, },
{ {
key: "forward_compact_mode", key: "forward_compact_mode",
label: "转发页面精简模式", label: "规则页面精简模式",
description: "开启后,转发页面列表使用 2.1.6-alpha8 样式(全局配置)", description: "开启后,规则页面列表使用 2.1.6-alpha8 样式(全局配置)",
type: "switch", type: "switch",
}, },
{ {
@@ -153,7 +153,7 @@ const BACKUP_TYPE_OPTIONS = [
{ value: "users", label: "用户" }, { value: "users", label: "用户" },
{ value: "nodes", label: "节点" }, { value: "nodes", label: "节点" },
{ value: "tunnels", label: "隧道" }, { value: "tunnels", label: "隧道" },
{ value: "forwards", label: "转发" }, { value: "forwards", label: "规则" },
{ value: "userTunnels", label: "用户隧道权限" }, { value: "userTunnels", label: "用户隧道权限" },
{ value: "speedLimits", label: "限速规则" }, { value: "speedLimits", label: "限速规则" },
{ value: "tunnelGroups", label: "隧道分组" }, { value: "tunnelGroups", label: "隧道分组" },
+8 -8
View File
@@ -616,7 +616,7 @@ export default function DashboardPage() {
</svg> </svg>
} }
iconClassName="bg-purple-100 dark:bg-purple-500/20" iconClassName="bg-purple-100 dark:bg-purple-500/20"
title="转发配额" title="规则配额"
value={formatNumber(userInfo.num || 0)} value={formatNumber(userInfo.num || 0)}
/> />
@@ -650,7 +650,7 @@ export default function DashboardPage() {
</svg> </svg>
} }
iconClassName="bg-orange-100 dark:bg-orange-500/20" iconClassName="bg-orange-100 dark:bg-orange-500/20"
title="已用转发" title="已用规则"
value={forwardList.length} value={forwardList.length}
/> />
</div> </div>
@@ -753,7 +753,7 @@ export default function DashboardPage() {
</div> </div>
<div> <div>
<p className="text-sm text-default-600 mb-1"> <p className="text-sm text-default-600 mb-1">
转发配额 规则配额
</p> </p>
<p className="font-semibold text-foreground"> <p className="font-semibold text-foreground">
{formatNumber(tunnel.num)} {formatNumber(tunnel.num)}
@@ -761,7 +761,7 @@ export default function DashboardPage() {
</div> </div>
<div> <div>
<p className="text-sm text-default-600 mb-1"> <p className="text-sm text-default-600 mb-1">
已用转发 已用规则
</p> </p>
<p className="font-semibold text-foreground"> <p className="font-semibold text-foreground">
{getTunnelUsedForwards(tunnel.tunnelId)} {getTunnelUsedForwards(tunnel.tunnelId)}
@@ -784,7 +784,7 @@ export default function DashboardPage() {
</Card> </Card>
)} )}
{/* 转发配置 */} {/* 规则配置 */}
<Card className="border border-gray-200 dark:border-default-200 shadow-md"> <Card className="border border-gray-200 dark:border-default-200 shadow-md">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -801,7 +801,7 @@ export default function DashboardPage() {
/> />
</svg> </svg>
<h2 className="text-lg lg:text-xl font-semibold text-foreground"> <h2 className="text-lg lg:text-xl font-semibold text-foreground">
转发配置 规则配置
</h2> </h2>
<span className="px-2 py-1 bg-default-100 dark:bg-default-50 text-default-600 rounded-full text-xs"> <span className="px-2 py-1 bg-default-100 dark:bg-default-50 text-default-600 rounded-full text-xs">
{forwardList.length} {forwardList.length}
@@ -825,7 +825,7 @@ export default function DashboardPage() {
strokeWidth={1.5} strokeWidth={1.5}
/> />
</svg> </svg>
<p className="text-default-500">暂无转发配置</p> <p className="text-default-500">暂无规则配置</p>
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
@@ -839,7 +839,7 @@ export default function DashboardPage() {
{group.tunnelName} {group.tunnelName}
</h3> </h3>
<span className="px-2 py-1 bg-primary-100 dark:bg-primary-500/20 text-primary-700 dark:text-primary-300 rounded-md text-sm"> <span className="px-2 py-1 bg-primary-100 dark:bg-primary-500/20 text-primary-700 dark:text-primary-300 rounded-md text-sm">
{group.forwards.length} 个转发 {group.forwards.length} 个规则
</span> </span>
</div> </div>
+93 -78
View File
@@ -675,7 +675,9 @@ export default function ForwardPage() {
return false; return false;
} }
const currentTunnel = allTunnels.find((tunnel) => tunnel.id === form.tunnelId); const currentTunnel = allTunnels.find(
(tunnel) => tunnel.id === form.tunnelId,
);
return (currentTunnel?.inNodeId?.length || 0) > 1; return (currentTunnel?.inNodeId?.length || 0) > 1;
}, [allTunnels, form.tunnelId]); }, [allTunnels, form.tunnelId]);
@@ -1174,7 +1176,7 @@ export default function ForwardPage() {
saveOrder(FORWARD_ORDER_KEY, order); saveOrder(FORWARD_ORDER_KEY, order);
} }
} else { } else {
toast.error(forwardsRes.msg || "获取转发列表失败"); toast.error(forwardsRes.msg || "获取规则列表失败");
} }
if (tunnelsRes.code === 0) { if (tunnelsRes.code === 0) {
@@ -1256,9 +1258,9 @@ export default function ForwardPage() {
const newErrors: { [key: string]: string } = {}; const newErrors: { [key: string]: string } = {};
if (!form.name.trim()) { if (!form.name.trim()) {
newErrors.name = "请输入转发名称"; newErrors.name = "请输入规则名称";
} else if (form.name.length < 2 || form.name.length > 50) { } else if (form.name.length < 2 || form.name.length > 50) {
newErrors.name = "转发名称长度应在2-50个字符之间"; newErrors.name = "规则名称长度应在2-50个字符之间";
} }
if (!form.tunnelId) { if (!form.tunnelId) {
@@ -1308,7 +1310,7 @@ export default function ForwardPage() {
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0;
}; };
// 新增转发 // 新增规则
const handleAdd = () => { const handleAdd = () => {
setIsEdit(false); setIsEdit(false);
setInIpTouched(false); setInIpTouched(false);
@@ -1326,7 +1328,7 @@ export default function ForwardPage() {
setModalOpen(true); setModalOpen(true);
}; };
// 编辑转发 // 编辑规则
const handleEdit = (forward: Forward) => { const handleEdit = (forward: Forward) => {
setIsEdit(true); setIsEdit(true);
setInIpTouched(false); setInIpTouched(false);
@@ -1352,7 +1354,7 @@ export default function ForwardPage() {
setDeleteModalOpen(true); setDeleteModalOpen(true);
}; };
// 确认删除转发 // 确认删除规则
const confirmDelete = async () => { const confirmDelete = async () => {
if (!forwardToDelete) return; if (!forwardToDelete) return;
@@ -1367,7 +1369,7 @@ export default function ForwardPage() {
} else { } else {
// 删除失败,询问是否强制删除 // 删除失败,询问是否强制删除
const confirmed = window.confirm( const confirmed = window.confirm(
`常规删除失败:${res.msg || "删除失败"}\n\n是否需要强制删除?\n\n⚠️ 注意:强制删除不会去验证节点端是否已经删除对应的转发服务。`, `常规删除失败:${res.msg || "删除失败"}\n\n是否需要强制删除?\n\n⚠️ 注意:强制删除不会去验证节点端是否已经删除对应的规则服务。`,
); );
if (confirmed) { if (confirmed) {
@@ -1489,7 +1491,7 @@ export default function ForwardPage() {
// 处理服务开关 // 处理服务开关
const handleServiceToggle = async (forward: Forward) => { const handleServiceToggle = async (forward: Forward) => {
if (forward.status !== 1 && forward.status !== 0) { if (forward.status !== 1 && forward.status !== 0) {
toast.error("转发状态异常,无法操作"); toast.error("规则状态异常,无法操作");
return; return;
} }
@@ -1514,7 +1516,7 @@ export default function ForwardPage() {
if (res.code === 0) { if (res.code === 0) {
toast.success(targetState ? "服务已启动" : "服务已暂停"); toast.success(targetState ? "服务已启动" : "服务已暂停");
// 更新转发状态 // 更新规则状态
setForwards((prev) => setForwards((prev) =>
prev.map((f) => prev.map((f) =>
f.id === forward.id ? { ...f, status: targetState ? 1 : 0 } : f, f.id === forward.id ? { ...f, status: targetState ? 1 : 0 } : f,
@@ -1540,7 +1542,7 @@ export default function ForwardPage() {
} }
}; };
// 诊断转发 // 诊断规则
const handleDiagnose = async (forward: Forward) => { const handleDiagnose = async (forward: Forward) => {
diagnosisAbortRef.current?.abort(); diagnosisAbortRef.current?.abort();
const abortController = new AbortController(); const abortController = new AbortController();
@@ -1795,7 +1797,7 @@ export default function ForwardPage() {
await copyToClipboard(allAddresses, "所有地址"); await copyToClipboard(allAddresses, "所有地址");
}; };
// 导出转发数据 // 导出规则数据
const handleExport = () => { const handleExport = () => {
setSelectedTunnelForExport(null); setSelectedTunnelForExport(null);
setExportData(""); setExportData("");
@@ -1813,13 +1815,13 @@ export default function ForwardPage() {
setExportLoading(true); setExportLoading(true);
try { try {
// 获取要导出的转发列表 // 获取要导出的规则列表
const forwardsToExport = sortedForwards.filter( const forwardsToExport = sortedForwards.filter(
(forward) => forward.tunnelId === selectedTunnelForExport, (forward) => forward.tunnelId === selectedTunnelForExport,
); );
if (forwardsToExport.length === 0) { if (forwardsToExport.length === 0) {
toast.error("所选隧道没有转发数据"); toast.error("所选隧道没有规则数据");
setExportLoading(false); setExportLoading(false);
return; return;
@@ -1842,10 +1844,10 @@ export default function ForwardPage() {
// 复制导出数据 // 复制导出数据
const copyExportData = async () => { const copyExportData = async () => {
await copyToClipboard(exportData, "转发数据"); await copyToClipboard(exportData, "规则数据");
}; };
// 导入转发数据 // 导入规则数据
const handleImport = () => { const handleImport = () => {
setImportData(""); setImportData("");
setImportResults([]); setImportResults([]);
@@ -1967,7 +1969,7 @@ export default function ForwardPage() {
{ {
line, line,
success: false, success: false,
message: "格式错误:需要至少包含目标地址和转发名称", message: "格式错误:需要至少包含目标地址和规则名称",
}, },
...prev, ...prev,
]); ]);
@@ -1981,7 +1983,7 @@ export default function ForwardPage() {
{ {
line, line,
success: false, success: false,
message: "目标地址和转发名称不能为空", message: "目标地址和规则名称不能为空",
}, },
...prev, ...prev,
]); ]);
@@ -2395,7 +2397,7 @@ export default function ForwardPage() {
}), }),
); );
// 根据排序顺序获取转发列表 // 根据排序顺序获取规则列表
const orderedForwards = useMemo((): Forward[] => { const orderedForwards = useMemo((): Forward[] => {
// 确保 forwards 数组存在且有效 // 确保 forwards 数组存在且有效
if (!forwards || forwards.length === 0) { if (!forwards || forwards.length === 0) {
@@ -2430,7 +2432,7 @@ export default function ForwardPage() {
); );
} }
// 确保过滤后的转发列表有效 // 确保过滤后的规则列表有效
if (!filteredForwards || filteredForwards.length === 0) { if (!filteredForwards || filteredForwards.length === 0) {
return []; return [];
} }
@@ -2464,7 +2466,7 @@ export default function ForwardPage() {
} }
}); });
// 添加不在排序列表中的转发(新添加的) // 添加不在排序列表中的规则(新添加的)
filteredForwards.forEach((forward) => { filteredForwards.forEach((forward) => {
if (!forwardOrder.includes(forward.id)) { if (!forwardOrder.includes(forward.id)) {
localSortedForwards.push(forward); localSortedForwards.push(forward);
@@ -2768,7 +2770,7 @@ export default function ForwardPage() {
<span className={titleClassName}>{tunnel.tunnelName}</span> <span className={titleClassName}>{tunnel.tunnelName}</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className={countClassName}>{tunnel.items.length} 条转发</span> <span className={countClassName}>{tunnel.items.length} 条规则</span>
<div <div
className="cursor-grab active:cursor-grabbing p-1 text-default-400 hover:text-default-600 transition-colors" className="cursor-grab active:cursor-grabbing p-1 text-default-400 hover:text-default-600 transition-colors"
title="拖拽分组排序" title="拖拽分组排序"
@@ -2791,7 +2793,7 @@ export default function ForwardPage() {
); );
}; };
// 可拖拽的转发卡片组件 // 可拖拽的规则卡片组件
const SortableForwardCard = ({ forward }: { forward: Forward }) => { const SortableForwardCard = ({ forward }: { forward: Forward }) => {
const { const {
attributes, attributes,
@@ -3292,7 +3294,7 @@ export default function ForwardPage() {
); );
}; };
// 渲染转发卡片 // 渲染规则卡片
const renderForwardCard = (forward: Forward, listeners?: any) => { const renderForwardCard = (forward: Forward, listeners?: any) => {
const statusDisplay = getStatusDisplay(forward.status); const statusDisplay = getStatusDisplay(forward.status);
const strategyDisplay = getStrategyDisplay(forward.strategy); const strategyDisplay = getStrategyDisplay(forward.strategy);
@@ -3585,7 +3587,7 @@ export default function ForwardPage() {
<div className="flex-1 max-w-sm flex items-center gap-2"> <div className="flex-1 max-w-sm flex items-center gap-2">
<SearchBar <SearchBar
isVisible={isSearchVisible} isVisible={isSearchVisible}
placeholder="搜索转发名称、地址或用户名" placeholder="搜索规则名称、地址或用户名"
value={searchKeyword} value={searchKeyword}
onChange={setSearchKeyword} onChange={setSearchKeyword}
onClose={() => setIsSearchVisible(false)} onClose={() => setIsSearchVisible(false)}
@@ -3811,7 +3813,7 @@ export default function ForwardPage() {
strategy={verticalListSortingStrategy} strategy={verticalListSortingStrategy}
> >
<Table <Table
aria-label="全部转发列表" aria-label="全部规则列表"
classNames={{ classNames={{
th: "bg-default-100/50 text-default-600 font-semibold text-sm border-b border-divider py-3 uppercase tracking-wider", th: "bg-default-100/50 text-default-600 font-semibold text-sm border-b border-divider py-3 uppercase tracking-wider",
td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0", td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0",
@@ -3833,7 +3835,10 @@ export default function ForwardPage() {
<TableColumn>状态</TableColumn> <TableColumn>状态</TableColumn>
<TableColumn className="text-right">操作</TableColumn> <TableColumn className="text-right">操作</TableColumn>
</TableHeader> </TableHeader>
<TableBody emptyContent="暂无转发配置" items={sortedForwards}> <TableBody
emptyContent="暂无规则配置"
items={sortedForwards}
>
{(forward) => ( {(forward) => (
<SortableCompactTableRow <SortableCompactTableRow
formatFlow={formatFlow} formatFlow={formatFlow}
@@ -3861,42 +3866,52 @@ export default function ForwardPage() {
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50"> <Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50">
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]"> <CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2"> <h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
暂无转发配置 暂无规则配置
</h3> </h3>
<p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed"> <p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed">
还没有创建任何转发配置,点击上方按钮开始创建 还没有创建任何规则配置,点击上方按钮开始创建
</p> </p>
</CardBody> </CardBody>
</Card> </Card>
) )
) : sortedForwards.length > 0 ? ( ) : sortedForwards.length > 0 ? (
<DndContext <>
collisionDetection={closestCenter} <div className="flex items-center justify-between px-1 mb-3">
sensors={sensors} <span className="text-sm font-semibold text-foreground">
onDragEnd={handleDragEnd} 全部规则
onDragStart={() => {}} </span>
> <span className="text-xs text-default-600">
<SortableContext {sortedForwards.length} 条规则
items={sortableForwardIds} </span>
strategy={rectSortingStrategy} </div>
<DndContext
collisionDetection={closestCenter}
sensors={sensors}
onDragEnd={handleDragEnd}
onDragStart={() => {}}
> >
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4"> <SortableContext
{sortedForwards.map((forward) => items={sortableForwardIds}
forward && forward.id ? ( strategy={rectSortingStrategy}
<SortableForwardCard key={forward.id} forward={forward} /> >
) : null, <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
)} {sortedForwards.map((forward) =>
</div> forward && forward.id ? (
</SortableContext> <SortableForwardCard key={forward.id} forward={forward} />
</DndContext> ) : null,
)}
</div>
</SortableContext>
</DndContext>
</>
) : ( ) : (
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50"> <Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50">
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]"> <CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2"> <h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
暂无转发配置 暂无规则配置
</h3> </h3>
<p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed"> <p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed">
还没有创建任何转发配置,点击上方按钮开始创建 还没有创建任何规则配置,点击上方按钮开始创建
</p> </p>
</CardBody> </CardBody>
</Card> </Card>
@@ -3929,7 +3944,7 @@ export default function ForwardPage() {
)} )}
</div> </div>
<span className="text-xs text-default-600"> <span className="text-xs text-default-600">
{groupForwardCount} 条转发 {groupForwardCount} 条规则
</span> </span>
</div> </div>
@@ -3984,7 +3999,7 @@ export default function ForwardPage() {
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
> >
<Table <Table
aria-label={`${group.userName}-${tunnel.tunnelName}转发列表`} aria-label={`${group.userName}-${tunnel.tunnelName}规则列表`}
className={`table-fixed ${FORWARD_GROUPED_TABLE_MIN_WIDTH_CLASS}`} className={`table-fixed ${FORWARD_GROUPED_TABLE_MIN_WIDTH_CLASS}`}
classNames={{ classNames={{
th: "bg-default-100/50 text-default-600 font-semibold text-sm border-b border-divider py-3 uppercase tracking-wider", th: "bg-default-100/50 text-default-600 font-semibold text-sm border-b border-divider py-3 uppercase tracking-wider",
@@ -4058,7 +4073,7 @@ export default function ForwardPage() {
</TableColumn> </TableColumn>
</TableHeader> </TableHeader>
<TableBody <TableBody
emptyContent="暂无转发配置" emptyContent="暂无规则配置"
items={tunnel.items} items={tunnel.items}
> >
{(forward) => ( {(forward) => (
@@ -4110,10 +4125,10 @@ export default function ForwardPage() {
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50"> <Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50">
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]"> <CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2"> <h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
暂无转发配置 暂无规则配置
</h3> </h3>
<p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed"> <p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed">
还没有创建任何转发配置,点击上方按钮开始创建 还没有创建任何规则配置,点击上方按钮开始创建
</p> </p>
</CardBody> </CardBody>
</Card> </Card>
@@ -4145,7 +4160,7 @@ export default function ForwardPage() {
)} )}
</div> </div>
<span className="text-xs text-default-600"> <span className="text-xs text-default-600">
{groupForwardCount} 条转发 {groupForwardCount} 条规则
</span> </span>
</div> </div>
@@ -4230,10 +4245,10 @@ export default function ForwardPage() {
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50"> <Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50">
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]"> <CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2"> <h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
暂无转发配置 暂无规则配置
</h3> </h3>
<p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed"> <p className="text-default-500 text-sm max-w-xs mx-auto leading-relaxed">
还没有创建任何转发配置,点击上方按钮开始创建 还没有创建任何规则配置,点击上方按钮开始创建
</p> </p>
</CardBody> </CardBody>
</Card> </Card>
@@ -4253,10 +4268,10 @@ export default function ForwardPage() {
<> <>
<ModalHeader className="flex flex-col gap-1"> <ModalHeader className="flex flex-col gap-1">
<h2 className="text-xl font-bold"> <h2 className="text-xl font-bold">
{isEdit ? "编辑转发" : "新增转发"} {isEdit ? "编辑规则" : "新增规则"}
</h2> </h2>
<p className="text-small text-default-500"> <p className="text-small text-default-500">
{isEdit ? "修改现有转发配置的信息" : "创建新的转发配置"} {isEdit ? "修改现有规则配置的信息" : "创建新的规则配置"}
</p> </p>
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@@ -4264,8 +4279,8 @@ export default function ForwardPage() {
<Input <Input
errorMessage={errors.name} errorMessage={errors.name}
isInvalid={!!errors.name} isInvalid={!!errors.name}
label="转发名称" label="规则名称"
placeholder="请输入转发名称" placeholder="请输入规则名称"
value={form.name} value={form.name}
variant="bordered" variant="bordered"
onChange={(e) => onChange={(e) =>
@@ -4369,10 +4384,10 @@ export default function ForwardPage() {
isCurrentTunnelMultiEntrance isCurrentTunnelMultiEntrance
? "多入口隧道使用节点默认IP" ? "多入口隧道使用节点默认IP"
: form.tunnelId : form.tunnelId
? currentTunnelIpOptions.length > 0 ? currentTunnelIpOptions.length > 0
? "选择入口监听IP" ? "选择入口监听IP"
: "当前隧道入口节点暂无可选IP" : "当前隧道入口节点暂无可选IP"
: "请先选择隧道" : "请先选择隧道"
} }
selectedKeys={[form.inIp || "__default__"]} selectedKeys={[form.inIp || "__default__"]}
variant="bordered" variant="bordered"
@@ -4441,7 +4456,7 @@ export default function ForwardPage() {
isLoading={submitLoading} isLoading={submitLoading}
onPress={handleSubmit} onPress={handleSubmit}
> >
{isEdit ? "保存修改" : "创建转发"} {isEdit ? "保存修改" : "创建规则"}
</Button> </Button>
</ModalFooter> </ModalFooter>
</> </>
@@ -4466,14 +4481,14 @@ export default function ForwardPage() {
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<p className="text-default-600"> <p className="text-default-600">
确定要删除转发{" "} 确定要删除规则{" "}
<span className="font-semibold text-foreground"> <span className="font-semibold text-foreground">
&quot;{forwardToDelete?.name}&quot; &quot;{forwardToDelete?.name}&quot;
</span>{" "} </span>{" "}
吗? 吗?
</p> </p>
<p className="text-small text-default-500 mt-2"> <p className="text-small text-default-500 mt-2">
此操作无法撤销,删除后该转发将永久消失。 此操作无法撤销,删除后该规则将永久消失。
</p> </p>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
@@ -4548,9 +4563,9 @@ export default function ForwardPage() {
> >
<ModalContent> <ModalContent>
<ModalHeader className="flex flex-col gap-1"> <ModalHeader className="flex flex-col gap-1">
<h2 className="text-xl font-bold">导出转发数据</h2> <h2 className="text-xl font-bold">导出规则数据</h2>
<p className="text-small text-default-500"> <p className="text-small text-default-500">
格式:目标地址|转发名称|入口端口 格式:目标地址|规则名称|入口端口
</p> </p>
</ModalHeader> </ModalHeader>
<ModalBody className="pb-6"> <ModalBody className="pb-6">
@@ -4700,11 +4715,11 @@ export default function ForwardPage() {
> >
<ModalContent> <ModalContent>
<ModalHeader className="flex flex-col gap-1"> <ModalHeader className="flex flex-col gap-1">
<h2 className="text-xl font-bold">导入转发数据</h2> <h2 className="text-xl font-bold">导入规则数据</h2>
{importFormat === "flvx" ? ( {importFormat === "flvx" ? (
<> <>
<p className="text-small text-default-500"> <p className="text-small text-default-500">
格式:目标地址|转发名称|入口端口,每行一个,入口端口留空将自动分配可用端口 格式:目标地址|规则名称|入口端口,每行一个,入口端口留空将自动分配可用端口
</p> </p>
<p className="text-small text-default-400"> <p className="text-small text-default-400">
目标地址支持单个地址(如:example.com:8080)或多个地址用逗号分隔(如:3.3.3.3:3,4.4.4.4:4) 目标地址支持单个地址(如:example.com:8080)或多个地址用逗号分隔(如:3.3.3.3:3,4.4.4.4:4)
@@ -4788,8 +4803,8 @@ export default function ForwardPage() {
minRows={8} minRows={8}
placeholder={ placeholder={
importFormat === "flvx" importFormat === "flvx"
? "请输入要导入的转发数据,格式:目标地址|转发名称|入口端口" ? "请输入要导入的规则数据,格式:目标地址|规则名称|入口端口"
: '请输入ny格式数据,每行一个JSON对象,如:{"dest":["1.2.3.4:80"],"listen_port":8080,"name":"转发1"};listen_port可省略自动分配' : '请输入ny格式数据,每行一个JSON对象,如:{"dest":["1.2.3.4:80"],"listen_port":8080,"name":"规则1"};listen_port可省略自动分配'
} }
value={importData} value={importData}
variant="flat" variant="flat"
@@ -4929,7 +4944,7 @@ export default function ForwardPage() {
{(onClose) => ( {(onClose) => (
<> <>
<ModalHeader className="flex flex-col gap-1 bg-content1 border-b border-divider"> <ModalHeader className="flex flex-col gap-1 bg-content1 border-b border-divider">
<h2 className="text-xl font-bold">转发诊断结果</h2> <h2 className="text-xl font-bold">规则诊断结果</h2>
{currentDiagnosisForward && ( {currentDiagnosisForward && (
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<span className="text-small text-default-500 truncate flex-1 min-w-0"> <span className="text-small text-default-500 truncate flex-1 min-w-0">
@@ -4941,7 +4956,7 @@ export default function ForwardPage() {
size="sm" size="sm"
variant="flat" variant="flat"
> >
转发服务 规则服务
</Chip> </Chip>
</div> </div>
)} )}
@@ -5500,7 +5515,7 @@ export default function ForwardPage() {
<ModalHeader>确认删除</ModalHeader> <ModalHeader>确认删除</ModalHeader>
<ModalBody> <ModalBody>
<p> <p>
确定要删除选中的 {selectedIds.size} 项转发吗?此操作不可撤销。 确定要删除选中的 {selectedIds.size} 项规则吗?此操作不可撤销。
</p> </p>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
@@ -5531,7 +5546,7 @@ export default function ForwardPage() {
<ModalHeader>隧道</ModalHeader> <ModalHeader>隧道</ModalHeader>
<ModalBody> <ModalBody>
<p className="mb-4"> <p className="mb-4">
将选中的 {selectedIds.size} 项转发迁移到新隧道: 将选中的 {selectedIds.size} 项规则迁移到新隧道:
</p> </p>
<Select <Select
label="目标隧道" label="目标隧道"
+3 -3
View File
@@ -129,7 +129,7 @@ export const SettingsPage = () => {
); );
if (response.code === 0) { if (response.code === 0) {
toast.success(`转发页面精简模式已${enabled ? "开启" : "关闭"}`); toast.success(`规则页面精简模式已${enabled ? "开启" : "关闭"}`);
window.dispatchEvent( window.dispatchEvent(
new CustomEvent("forwardCompactModeChanged", { new CustomEvent("forwardCompactModeChanged", {
detail: { enabled }, detail: { enabled },
@@ -232,10 +232,10 @@ export const SettingsPage = () => {
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<div> <div>
<p className="text-sm font-medium text-gray-900 dark:text-white"> <p className="text-sm font-medium text-gray-900 dark:text-white">
转发页面精简模式 规则页面精简模式
</p> </p>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400"> <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
开启后,转发页面列表使用 2.1.6-alpha8 样式。 开启后,规则页面列表使用 2.1.6-alpha8 样式。{" "}
</p> </p>
</div> </div>
<Switch <Switch
+254 -232
View File
@@ -1843,10 +1843,10 @@ export default function TunnelPage() {
isMultiNodeGroup isMultiNodeGroup
? "多节点跳使用节点默认IP" ? "多节点跳使用节点默认IP"
: groupSelectedNodeIds.length === 0 : groupSelectedNodeIds.length === 0
? "请先选择节点" ? "请先选择节点"
: groupIpOptions.length > 0 : groupIpOptions.length > 0
? "选择连接IP" ? "选择连接IP"
: "所选节点无共同可选IP" : "所选节点无共同可选IP"
} }
selectedKeys={[ selectedKeys={[
selectedGroupConnectIp || "__default__", selectedGroupConnectIp || "__default__",
@@ -1906,243 +1906,259 @@ export default function TunnelPage() {
return ( return (
<> <>
<div className="grid grid-cols-1 md:grid-cols-4 gap-2"> <div className="grid grid-cols-1 md:grid-cols-4 gap-2">
{/* 节点选择 - 移动端100%,桌面端50% */} {/* 节点选择 - 移动端100%,桌面端50% */}
<div className="col-span-1 md:col-span-2"> <div className="col-span-1 md:col-span-2">
<Select <Select
classNames={{ classNames={{
label: "text-xs", label: "text-xs",
value: "text-sm", value: "text-sm",
}} }}
disabledKeys={[ disabledKeys={[
...nodes ...nodes
.filter((node) => node.status !== 1) .filter((node) => node.status !== 1)
.map((node) => node.id.toString()), .map((node) => node.id.toString()),
...form.inNodeId.map((ct) => ...form.inNodeId.map((ct) =>
ct.nodeId.toString(), ct.nodeId.toString(),
), ),
...getSelectedChainNodeIds().map((id) => ...getSelectedChainNodeIds().map((id) =>
id.toString(), id.toString(),
), ),
]} ]}
dropdownPlacement="top" dropdownPlacement="top"
errorMessage={errors.outNodeId} errorMessage={errors.outNodeId}
isInvalid={!!errors.outNodeId} isInvalid={!!errors.outNodeId}
label="节点" label="节点"
placeholder="请选择出口节点(可多选)" placeholder="请选择出口节点(可多选)"
selectedKeys={ selectedKeys={
form.outNodeId form.outNodeId
? form.outNodeId ? form.outNodeId
.filter((ct) => ct.nodeId !== -1) .filter((ct) => ct.nodeId !== -1)
.map((ct) => ct.nodeId.toString()) .map((ct) => ct.nodeId.toString())
: [] : []
} }
selectionMode="multiple" selectionMode="multiple"
variant="bordered" variant="bordered"
onSelectionChange={(keys) => { onSelectionChange={(keys) => {
const selectedIds = toSelectedNodeIds(keys); const selectedIds = toSelectedNodeIds(keys);
setForm((prev) => { setForm((prev) => {
const currentOutNodes = prev.outNodeId || []; const currentOutNodes =
const protocol = prev.outNodeId || [];
currentOutNodes[0]?.protocol || "tls"; const protocol =
const strategy = currentOutNodes[0]?.protocol || "tls";
currentOutNodes[0]?.strategy || "round"; const strategy =
const realNodes = currentOutNodes.filter( currentOutNodes[0]?.strategy || "round";
(ct) => ct.nodeId !== -1, const realNodes = currentOutNodes.filter(
); (ct) => ct.nodeId !== -1,
);
return { return {
...prev, ...prev,
outNodeId: mergeOrderedNodes( outNodeId: mergeOrderedNodes(
realNodes, realNodes,
selectedIds, selectedIds,
(nodeId) => ({ (nodeId) => ({
nodeId, nodeId,
chainType: 3, chainType: 3,
protocol, protocol,
strategy, strategy,
}), }),
), ),
}; };
}); });
}} }}
> >
{nodes.map((node) => ( {nodes.map((node) => (
<SelectItem <SelectItem
key={node.id} key={node.id}
textValue={`${node.name}`} textValue={`${node.name}`}
>
<div className="flex items-center justify-between">
<span>{node.name}</span>
<div className="flex items-center gap-2">
<Chip
color={
node.status === 1
? "success"
: "default"
}
size="sm"
variant="flat"
> >
{node.status === 1 ? "在线" : "离线"} <div className="flex items-center justify-between">
</Chip> <span>{node.name}</span>
{form.inNodeId.some( <div className="flex items-center gap-2">
(ct) => ct.nodeId === node.id, <Chip
) && ( color={
<Chip node.status === 1
color="warning" ? "success"
size="sm" : "default"
variant="flat" }
> size="sm"
已选为入口 variant="flat"
</Chip> >
)} {node.status === 1
{getSelectedChainNodeIds().includes( ? "在线"
node.id, : "离线"}
) && ( </Chip>
<Chip {form.inNodeId.some(
color="primary" (ct) => ct.nodeId === node.id,
size="sm" ) && (
variant="flat" <Chip
> color="warning"
已选为转发链 size="sm"
</Chip> variant="flat"
)} >
</div> 已选为入口
</div> </Chip>
</SelectItem> )}
))} {getSelectedChainNodeIds().includes(
</Select> node.id,
</div> ) && (
<Chip
color="primary"
size="sm"
variant="flat"
>
已选为转发链
</Chip>
)}
</div>
</div>
</SelectItem>
))}
</Select>
</div>
{/* 协议选择 - 25% */} {/* 协议选择 - 25% */}
<Select <Select
classNames={{ classNames={{
label: "text-xs", label: "text-xs",
value: "text-sm", value: "text-sm",
}} }}
errorMessage={errors.protocol} errorMessage={errors.protocol}
isInvalid={!!errors.protocol} isInvalid={!!errors.protocol}
label="协议" label="协议"
placeholder="选择协议" placeholder="选择协议"
selectedKeys={[ selectedKeys={[
(() => { (() => {
if ( if (
!form.outNodeId || !form.outNodeId ||
form.outNodeId.length === 0 form.outNodeId.length === 0
) )
return "tls"; return "tls";
return form.outNodeId[0].protocol || "tls"; return form.outNodeId[0].protocol || "tls";
})(), })(),
]} ]}
variant="bordered" variant="bordered"
onSelectionChange={(keys) => { onSelectionChange={(keys) => {
const selectedKey = Array.from(keys)[0] as string; const selectedKey = Array.from(
keys,
)[0] as string;
if (selectedKey) { if (selectedKey) {
setForm((prev) => { setForm((prev) => {
const currentOutNodes = prev.outNodeId || []; const currentOutNodes =
const currentStrategy = prev.outNodeId || [];
currentOutNodes.length > 0 const currentStrategy =
? currentOutNodes[0].strategy || "round" currentOutNodes.length > 0
: "round"; ? currentOutNodes[0].strategy ||
"round"
: "round";
if (currentOutNodes.length === 0) { if (currentOutNodes.length === 0) {
// 如果还没有出口节点,创建一个占位节点保存设置 // 如果还没有出口节点,创建一个占位节点保存设置
return { return {
...prev, ...prev,
outNodeId: [ outNodeId: [
{ {
nodeId: -1, nodeId: -1,
chainType: 3, chainType: 3,
protocol: selectedKey, protocol: selectedKey,
strategy: currentStrategy, strategy: currentStrategy,
}, },
], ],
}; };
} }
// 更新所有出口节点的协议 // 更新所有出口节点的协议
return { return {
...prev, ...prev,
outNodeId: currentOutNodes.map((ct) => ({ outNodeId: currentOutNodes.map(
...ct, (ct) => ({
protocol: selectedKey, ...ct,
})), protocol: selectedKey,
}; }),
}); ),
} };
}} });
> }
<SelectItem key="tls">TLS</SelectItem> }}
<SelectItem key="wss">WSS</SelectItem> >
<SelectItem key="tcp">TCP</SelectItem> <SelectItem key="tls">TLS</SelectItem>
<SelectItem key="mtls">MTLS</SelectItem> <SelectItem key="wss">WSS</SelectItem>
<SelectItem key="mwss">MWSS</SelectItem> <SelectItem key="tcp">TCP</SelectItem>
<SelectItem key="mtcp">MTCP</SelectItem> <SelectItem key="mtls">MTLS</SelectItem>
</Select> <SelectItem key="mwss">MWSS</SelectItem>
<SelectItem key="mtcp">MTCP</SelectItem>
</Select>
{/* 负载策略 - 25% */} {/* 负载策略 - 25% */}
<Select <Select
classNames={{ classNames={{
label: "text-xs", label: "text-xs",
value: "text-sm", value: "text-sm",
}} }}
label="负载策略" label="负载策略"
placeholder="选择策略" placeholder="选择策略"
selectedKeys={[ selectedKeys={[
(() => { (() => {
if ( if (
!form.outNodeId || !form.outNodeId ||
form.outNodeId.length === 0 form.outNodeId.length === 0
) )
return "round"; return "round";
return form.outNodeId[0].strategy || "round"; return (
})(), form.outNodeId[0].strategy || "round"
]} );
variant="bordered" })(),
onSelectionChange={(keys) => { ]}
const selectedKey = Array.from(keys)[0] as string; variant="bordered"
onSelectionChange={(keys) => {
const selectedKey = Array.from(
keys,
)[0] as string;
if (selectedKey) { if (selectedKey) {
setForm((prev) => { setForm((prev) => {
const currentOutNodes = prev.outNodeId || []; const currentOutNodes =
const currentProtocol = prev.outNodeId || [];
currentOutNodes.length > 0 const currentProtocol =
? currentOutNodes[0].protocol || "tls" currentOutNodes.length > 0
: "tls"; ? currentOutNodes[0].protocol || "tls"
: "tls";
if (currentOutNodes.length === 0) { if (currentOutNodes.length === 0) {
return { return {
...prev, ...prev,
outNodeId: [ outNodeId: [
{ {
nodeId: -1, nodeId: -1,
chainType: 3, chainType: 3,
protocol: currentProtocol, protocol: currentProtocol,
strategy: selectedKey, strategy: selectedKey,
}, },
], ],
}; };
} }
return { return {
...prev, ...prev,
outNodeId: currentOutNodes.map((ct) => ({ outNodeId: currentOutNodes.map(
...ct, (ct) => ({
strategy: selectedKey, ...ct,
})), strategy: selectedKey,
}; }),
}); ),
} };
}} });
> }
<SelectItem key="fifo">主备</SelectItem> }}
<SelectItem key="round">轮询</SelectItem> >
<SelectItem key="rand">随机</SelectItem> <SelectItem key="fifo">主备</SelectItem>
</Select> <SelectItem key="round">轮询</SelectItem>
<SelectItem key="rand">随机</SelectItem>
</Select>
</div> </div>
{/* 连接IP - 出口节点 */} {/* 连接IP - 出口节点 */}
@@ -2179,9 +2195,13 @@ export default function TunnelPage() {
size="sm" size="sm"
variant="bordered" variant="bordered"
onSelectionChange={(keys) => { onSelectionChange={(keys) => {
const selectedKey = Array.from(keys)[0] as string; const selectedKey = Array.from(
keys,
)[0] as string;
const value = const value =
selectedKey === "__default__" ? "" : selectedKey; selectedKey === "__default__"
? ""
: selectedKey;
setForm((prev) => { setForm((prev) => {
const currentOutNodes = prev.outNodeId || []; const currentOutNodes = prev.outNodeId || [];
@@ -2211,7 +2231,9 @@ export default function TunnelPage() {
}); });
}} }}
> >
<SelectItem key="__default__">默认连接IP</SelectItem> <SelectItem key="__default__">
默认连接IP
</SelectItem>
{commonOutIpOptions.map((ip) => ( {commonOutIpOptions.map((ip) => (
<SelectItem key={ip}>{ip}</SelectItem> <SelectItem key={ip}>{ip}</SelectItem>
))} ))}
@@ -2887,7 +2909,7 @@ export default function TunnelPage() {
<ModalBody> <ModalBody>
<p> <p>
确定要删除选中的 {selectedIds.size}{" "} 确定要删除选中的 {selectedIds.size}{" "}
项隧道吗?此操作不可撤销,相关转发也将被删除。 项隧道吗?此操作不可撤销,相关规则也将被删除。
</p> </p>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
+6 -6
View File
@@ -845,7 +845,7 @@ export default function UserPage() {
{/* 其他信息 */} {/* 其他信息 */}
<div className="space-y-1.5 pt-2 border-t border-divider"> <div className="space-y-1.5 pt-2 border-t border-divider">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-default-600">转发数量</span> <span className="text-default-600">规则数量</span>
<span className="font-medium text-xs"> <span className="font-medium text-xs">
{user.num} {user.num}
</span> </span>
@@ -1001,7 +1001,7 @@ export default function UserPage() {
/> />
<Input <Input
isRequired isRequired
label="转发数量" label="规则数量"
max="99999" max="99999"
min="1" min="1"
type="number" type="number"
@@ -1138,7 +1138,7 @@ export default function UserPage() {
<h3 className="text-lg font-semibold mb-4">分配新权限</h3> <h3 className="text-lg font-semibold mb-4">分配新权限</h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="text-sm text-default-500 bg-default-100 dark:bg-default-50 p-3 rounded-lg border border-default-200 dark:border-default-100/30"> <div className="text-sm text-default-500 bg-default-100 dark:bg-default-50 p-3 rounded-lg border border-default-200 dark:border-default-100/30">
流量限制、转发数量、到期时间、流量重置时间将自动继承用户设置 流量限制、规则数量、到期时间、流量重置时间将自动继承用户设置
</div> </div>
<div className="grid gap-2 max-h-72 overflow-y-auto pr-1"> <div className="grid gap-2 max-h-72 overflow-y-auto pr-1">
@@ -1296,7 +1296,7 @@ export default function UserPage() {
<TableHeader> <TableHeader>
<TableColumn>隧道名称</TableColumn> <TableColumn>隧道名称</TableColumn>
<TableColumn>流量统计</TableColumn> <TableColumn>流量统计</TableColumn>
<TableColumn>转发数量</TableColumn> <TableColumn>规则数量</TableColumn>
<TableColumn>状态</TableColumn> <TableColumn>状态</TableColumn>
<TableColumn>限速规则</TableColumn> <TableColumn>限速规则</TableColumn>
<TableColumn>重置时间</TableColumn> <TableColumn>重置时间</TableColumn>
@@ -1453,7 +1453,7 @@ export default function UserPage() {
/> />
<Input <Input
label="转发数量" label="规则数量"
max="99999" max="99999"
min="1" min="1"
type="number" type="number"
@@ -1668,7 +1668,7 @@ export default function UserPage() {
的权限吗? 的权限吗?
</p> </p>
<p className="text-small text-default-500 mt-1"> <p className="text-small text-default-500 mt-1">
删除后该用户将无法使用此隧道创建转发,此操作不可撤销。 删除后该用户将无法使用此隧道创建规则,此操作不可撤销。{" "}
</p> </p>
</div> </div>
</div> </div>