From 5398486fdfae13e24c4783b827b5981671e7686d Mon Sep 17 00:00:00 2001 From: Aska Date: Mon, 7 Sep 2026 15:45:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=B8=A0=E9=81=93=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=A1=A8=E5=8D=95=E6=8E=92=E7=89=88+=E5=BC=95?= =?UTF-8?q?=E5=AF=BC=E2=80=94=E2=80=94=E6=A0=B8=E5=BF=83=E5=BF=85=E5=A1=AB?= =?UTF-8?q?(token)+=E9=AB=98=E7=BA=A7=E6=8A=98=E5=8F=A0+=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E7=8B=AC=E7=AB=8B=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agent-118 排版引导: - 核心必填区: 高亮 Callout(各平台'只需填X'头条)+Token必填badge(红底)+自动获取旁注, 不再藏段落 - 高级选项折叠(Miuix arrow折叠): base_url/平台特定字段/额外 config(连接配置), 收起默认 - 各平台引导头条: kook'只需填bot_token其余自动获取'/telegram/qq(mode接入方式)/weixin/webhook 我补(主人原则: 连接配置 vs 业务策略分开): - 黑白名单(allow/deny groups/users)+限流(groupMsgPerMin/msgPerMin/maxMessages) 从'高级选项'抽离成独立'业务策略'折叠区(不参与端到端连接) - openConfig 重置 polOpen; 业务策略默认收起 - 拆 channelFields.ts(PlatformField/PLATFORM_FIELDS/PLATFORM_GUIDE) → Channels.tsx 454行<500红线合规 前端 build 绿; 敏感字段password掩码/不回显 --- frontend/web/src/admin/modules/Channels.tsx | 242 +++++++++--------- .../web/src/admin/modules/channelFields.ts | 70 +++++ 2 files changed, 186 insertions(+), 126 deletions(-) create mode 100644 frontend/web/src/admin/modules/channelFields.ts diff --git a/frontend/web/src/admin/modules/Channels.tsx b/frontend/web/src/admin/modules/Channels.tsx index 06c7165..9b7ceb6 100644 --- a/frontend/web/src/admin/modules/Channels.tsx +++ b/frontend/web/src/admin/modules/Channels.tsx @@ -13,57 +13,14 @@ import { useAutoReload } from "../hooks"; import { useAuditStore } from "../audit"; import { AdminTable, Notice, PageHeader, type AdminColumn } from "../components"; import { Button, Surface, Tag } from "../../components/primitives"; +import { Icon } from "../../components/Icon"; +import { PLATFORM_FIELDS, PLATFORM_GUIDE } from "./channelFields"; /** Platform part of an adapter name ("kook:conf0" → "kook"). */ function platformOf(name: string): string { return name.split(":")[0]; } -/** One curated config input for a platform. `key` is the config-map JSON key - * the backend decodes (see plugins/channels/* adapter Config json tags). */ -interface PlatformField { - key: string; - label: string; - placeholder?: string; - /** Render as a masked [type=password] input (token / verify / encrypt / secret). */ - secret?: boolean; -} - -/** Platform-specific config fields shown for a platform. The COMMON fields - * (account id, token/bot_token, baseURL/base_url, allow/deny lists, flow limits) - * are rendered for every platform separately; this map holds only what a given - * adapter additionally needs. Keys are the adapter Config json tags verbatim. */ -const PLATFORM_FIELDS: Record = { - // KOOK is "token 即用、其余自动获取": only the token is required. base_url - // defaults to the official root, receive_mode defaults to "ws" and webhook_path - // to "/webhook/kook", so none of them need a form field. Advanced values - // (receive_mode="webhook", webhook_path, webhook_verify_token, webhook_encrypt_key) - // are set through the "额外 config" JSON instead. We keep the (empty) entry so - // the panel still renders the KOOK-specific hint below. - kook: [], - "qq-obv11": [ - { key: "mode", label: "mode(client/server)", placeholder: "server" }, - { key: "listen_addr", label: "listen_addr(server 监听)", placeholder: "0.0.0.0:6700" }, - { key: "remote_url", label: "remote_url(client 连接)", placeholder: "ws://…" }, - { key: "access_token", label: "access_token(OneBot 访问 token)", secret: true }, - { key: "self_id", label: "self_id(机器人 QQ 号)", placeholder: "123456789" }, - ], - telegram: [{ key: "webhook_url", label: "webhook_url(公网回调地址)", placeholder: "https://…" }], - "weixin-ilink": [ - { key: "cdn_base_url", label: "cdn_base_url(媒体 CDN)", placeholder: "https://…" }, - { key: "bot_id", label: "bot_id(ilink_bot_id)", placeholder: "wx…" }, - { key: "owner_id", label: "owner_id(绑定主人 uid)", placeholder: "wx…" }, - { key: "cursor", label: "cursor(get_updates_buf)", placeholder: "…" }, - ], - webhook: [ - { key: "endpointPath", label: "endpointPath(挂载路由)", placeholder: "/webhook" }, - { key: "channelId", label: "channelId(渠道 id)", placeholder: "…" }, - { key: "channelName", label: "channelName(渠道名)", placeholder: "…" }, - { key: "secret", label: "secret(HMAC 签名密钥)", secret: true }, - { key: "signatureHeader", label: "signatureHeader(签名头)", placeholder: "X-Webhook-Signature" }, - ], -}; - export function ChannelsModule(): React.ReactNode { const { data, loading, error, reload } = useAsyncData(adminListChannels, []); useAutoReload(reload, 10000); @@ -94,6 +51,11 @@ export function ChannelsModule(): React.ReactNode { const [msgPerMin, setMsgPerMin] = useState(""); const [maxMessages, setMaxMessages] = useState(""); const [extraConfig, setExtraConfig] = useState(""); + // Advanced-options fold; default closed (values persist in state regardless). + const [advOpen, setAdvOpen] = useState(false); + // Business-policy fold (blacklist/allow + rate limits) is separate from the + // connection config — it governs admission/de-noise, not the endpoint. + const [polOpen, setPolOpen] = useState(false); const toggle = (name: string, connect: boolean) => { setBusy(name); @@ -157,6 +119,8 @@ export function ChannelsModule(): React.ReactNode { setMsgPerMin(cfg.msgPerMin == null ? "" : String(cfg.msgPerMin)); setMaxMessages(cfg.maxMessages == null ? "" : String(cfg.maxMessages)); setExtraConfig(""); + setAdvOpen(false); // fold stays closed on open; advanced values are prefilled. + setPolOpen(false); // business-policy fold also starts closed. }; const save = () => { @@ -165,10 +129,8 @@ export function ChannelsModule(): React.ReactNode { setErr(null); setNote(null); const toList = (s: string) => s.split(/[\n,]/).map((x) => x.trim()).filter(Boolean); - // Curate this platform's specific config keys (e.g. kook receive_mode / - // webhook_path, qq mode / listen_addr), then let an explicit extraConfig JSON - // override any of them. Keys are the adapter Config json tags the backend - // DecodeConfig reads, so the saved config lands in the right struct fields. + // Curate platform-specific config keys, then let an explicit extraConfig JSON + // override any of them; these json tags are what the backend DecodeConfig reads. const specificMap: Record = {}; for (const f of PLATFORM_FIELDS[cfgPlatform] ?? []) { const v = (platVals[f.key] ?? "").trim(); @@ -333,90 +295,92 @@ export function ChannelsModule(): React.ReactNode { {cfgPlatform ? (

配置账号 → {cfgPlatform}

- {cfgPlatform === "kook" ? ( -

- KOOK 为「token 即用、其余自动获取」:仅需填 token(必填);base_url 留空即用官方默认;receive_mode 默认 ws、webhook_path 默认 /webhook/kook,均无需填写。若改用 webhook 或配校验/加密,在下方「额外 config」JSON 里设置,如 - {" "} - {"{"}"receive_mode":"webhook","webhook_path":"/webhook/kook","webhook_verify_token":"...","webhook_encrypt_key":"..."{"}"} - (verify_token / encrypt_key 仅在 webhook 模式需要)。连接后渠道列表会显示该 KOOK bot 身份。 -

- ) : null}

保存后写入 channels.json(本地、已 gitignore)并热更新 {cfgPlatform} 的渠道 adapter,无需重启进程。token 只在写文件时出现,列表/响应不回显裸值。

-
- - - - {(PLATFORM_FIELDS[cfgPlatform] ?? []).map((f) => ( -