From 89b3f5d843e9e5879656b839eb7d4c3cc6fde311 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jun 2026 20:21:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(wavelet):=20=E5=AE=9E=E8=A3=85=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=20PoW=20=E9=85=8D=E7=BD=AE=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 迁移旧前端 PoW 难度/TTL 与 IP/路径/UA 黑白名单编辑,替换占位提示。 --- .../detail/components/auth-section.tsx | 112 ++------- .../waf/components/pow-config-panel.tsx | 237 ++++++++++++++++-- docs/changelog/index.md | 1 + 3 files changed, 241 insertions(+), 109 deletions(-) diff --git a/Wavelet/frontend/app/(main)/proxy-routes/detail/components/auth-section.tsx b/Wavelet/frontend/app/(main)/proxy-routes/detail/components/auth-section.tsx index 47576f37..67f3103c 100644 --- a/Wavelet/frontend/app/(main)/proxy-routes/detail/components/auth-section.tsx +++ b/Wavelet/frontend/app/(main)/proxy-routes/detail/components/auth-section.tsx @@ -45,11 +45,11 @@ const authSchema = z .superRefine((value, context) => { if (value.auth_mode === 'pow') { const difficulty = Number(value.pow_difficulty); - if (!Number.isFinite(difficulty) || difficulty < 1 || difficulty > 10) { + if (!Number.isFinite(difficulty) || difficulty < 1 || difficulty > 16) { context.addIssue({ code: z.ZodIssueCode.custom, path: ['pow_difficulty'], - message: '难度需为 1-10 的整数', + message: '难度需为 1-16 的整数', }); } @@ -102,18 +102,14 @@ function resolveAuthMode(route: ProxyRouteItem): AuthValues['auth_mode'] { return 'none'; } -function buildPowConfig( - values: AuthValues, - route: ProxyRouteItem, - currentConfig: ProxyRoutePoWConfig, -): ProxyRoutePoWConfig { - return { - ...currentConfig, - difficulty: Number(values.pow_difficulty), - algorithm: values.pow_algorithm, - session_ttl: Number(values.pow_session_ttl), - challenge_ttl: Number(values.pow_challenge_ttl), - }; +function syncPowFormValues( + setValue: ReturnType>['setValue'], + config: ProxyRoutePoWConfig, +) { + setValue('pow_difficulty', String(config.difficulty)); + setValue('pow_algorithm', config.algorithm); + setValue('pow_session_ttl', String(config.session_ttl)); + setValue('pow_challenge_ttl', String(config.challenge_ttl)); } interface AuthSectionProps { @@ -171,7 +167,8 @@ export function AuthSection({ route, onRouteUpdate, onSavingChange }: AuthSectio id={proxyRouteFormIds.auth} className="space-y-5" onSubmit={form.handleSubmit(async (values) => { - const nextPowConfig = buildPowConfig(values, route, powConfig); + const nextPowConfig = + values.auth_mode === 'pow' ? powConfig : route.pow_config ?? defaultPowConfig; const powEnabled = values.auth_mode === 'pow' || values.pow_enabled; await save( @@ -247,82 +244,15 @@ export function AuthSection({ route, onRouteUpdate, onSavingChange }: AuthSectio ) : null} {authMode === 'pow' ? ( -
- { - form.setValue('pow_enabled', enabled); - setPowConfig(config); - }} - /> - -
- ( - - 难度 - - - - - - )} - /> - - ( - - 算法 - - - - )} - /> - - ( - - 会话 TTL (秒) - - - - - - )} - /> - - ( - - 挑战 TTL (秒) - - - - - - )} - /> -
-
+ { + form.setValue('pow_enabled', enabled); + setPowConfig(config); + syncPowFormValues(form.setValue, config); + }} + /> ) : null} diff --git a/Wavelet/frontend/app/(main)/waf/components/pow-config-panel.tsx b/Wavelet/frontend/app/(main)/waf/components/pow-config-panel.tsx index 2ec9f399..7a46ea17 100644 --- a/Wavelet/frontend/app/(main)/waf/components/pow-config-panel.tsx +++ b/Wavelet/frontend/app/(main)/waf/components/pow-config-panel.tsx @@ -1,43 +1,244 @@ 'use client'; -import {Info} from 'lucide-react'; +import {useEffect, useState} from 'react'; -import {Switch} from '@/components/ui/switch'; +import {Input} from '@/components/ui/input'; import {Label} from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import {Switch} from '@/components/ui/switch'; +import {Textarea} from '@/components/ui/textarea'; import type {ProxyRoutePoWConfig} from '@/lib/services/openflare'; +import {listToText, parseTextareaList} from './helpers'; + interface PowConfigPanelProps { enabled: boolean; config: ProxyRoutePoWConfig; onChange: (enabled: boolean, config: ProxyRoutePoWConfig) => void; } -export function PowConfigPanel({ enabled, config, onChange }: PowConfigPanelProps) { - return ( -
-
- -
-

PoW 配置面板(占位)

-

- 完整 PoW 黑白名单与难度参数编辑将在后续迭代中实装。当前可先切换启用状态并保存规则组。 -

-
-
+type PowListDraft = { + whitelist: Record; + blacklist: Record; +}; +function buildDraft(config: ProxyRoutePoWConfig): PowListDraft { + return { + whitelist: { + ips: listToText(config.whitelist?.ips), + ip_cidrs: listToText(config.whitelist?.ip_cidrs), + paths: listToText(config.whitelist?.paths), + path_regexes: listToText(config.whitelist?.path_regexes), + user_agents: listToText(config.whitelist?.user_agents), + }, + blacklist: { + ips: listToText(config.blacklist?.ips), + ip_cidrs: listToText(config.blacklist?.ip_cidrs), + paths: listToText(config.blacklist?.paths), + path_regexes: listToText(config.blacklist?.path_regexes), + user_agents: listToText(config.blacklist?.user_agents), + }, + }; +} + +function applyDraft( + config: ProxyRoutePoWConfig, + draft: PowListDraft, +): ProxyRoutePoWConfig { + return { + ...config, + whitelist: { + ips: parseTextareaList(draft.whitelist.ips), + ip_cidrs: parseTextareaList(draft.whitelist.ip_cidrs), + paths: parseTextareaList(draft.whitelist.paths), + path_regexes: parseTextareaList(draft.whitelist.path_regexes), + user_agents: parseTextareaList(draft.whitelist.user_agents), + }, + blacklist: { + ips: parseTextareaList(draft.blacklist.ips), + ip_cidrs: parseTextareaList(draft.blacklist.ip_cidrs), + paths: parseTextareaList(draft.blacklist.paths), + path_regexes: parseTextareaList(draft.blacklist.path_regexes), + user_agents: parseTextareaList(draft.blacklist.user_agents), + }, + }; +} + +function PowListCard({ + title, + scope, + draft, + onUpdate, +}: { + title: string; + scope: keyof PowListDraft; + draft: PowListDraft; + onUpdate: ( + scope: keyof PowListDraft, + key: keyof ProxyRoutePoWConfig['whitelist'], + value: string, + ) => void; +}) { + const fields: Array<{ + key: keyof ProxyRoutePoWConfig['whitelist']; + label: string; + placeholder: string; + }> = [ + {key: 'ips', label: 'IP', placeholder: '每行一个 IP'}, + {key: 'ip_cidrs', label: 'IP CIDR', placeholder: '每行一个网段,如 10.0.0.0/8'}, + {key: 'paths', label: '路径', placeholder: '每行一个路径前缀'}, + {key: 'path_regexes', label: '路径正则', placeholder: '每行一个正则表达式'}, + {key: 'user_agents', label: 'User-Agent', placeholder: '每行一个 UA 关键词或正则'}, + ]; + + return ( +
+

{title}

+
+ {fields.map((field) => ( +
+ +