fix(web): 渠道配置表单按平台动态渲染字段——不再混显其他适配器字段
- 建 PLATFORM_FIELDS 平台→字段映射(key 严格用各 adapter Config json tag,webhook 实为 camelCase 已校正) - 表单按 cfgPlatform 只渲染该平台特有字段(kook: receive_mode/webhook_path/verify_token/encrypt_key + 通用; qq: mode/listen_addr/remote_url/access_token/self_id + 通用; telegram/weixin 各自) + 通用(账号id/Token(密码掩码)/baseURL) + 黑白名单/限流 + extraConfig - 删无条件渲染的 mode/remoteURL/listenAddr(原 bug); 保存按 cfgPlatform 组装 config 含平台特有字段; 敏感字段 type=password 掩码 - fix: 点开 kook 配置不再出现 qq 的 mode/remoteURL/listenAddr 等 - npm run build 绿(81模块)
This commit is contained in:
1 parent
617157262b
commit
f5ae953ddc
1 file changed
+82
-26
@@ -19,6 +19,50 @@ 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<string, PlatformField[]> = {
|
||||
kook: [
|
||||
{ key: "receive_mode", label: "receive_mode(收包模式 ws/webhook)", placeholder: "ws" },
|
||||
{ key: "webhook_path", label: "webhook_path(webhook 挂载路由)", placeholder: "/webhook/kook" },
|
||||
{ key: "webhook_verify_token", label: "webhook_verify_token(校验 token)", secret: true },
|
||||
{ key: "webhook_encrypt_key", label: "webhook_encrypt_key(AES 加密密钥)", secret: true },
|
||||
],
|
||||
"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);
|
||||
@@ -37,9 +81,10 @@ export function ChannelsModule(): React.ReactNode {
|
||||
const [cfgId, setCfgId] = useState("");
|
||||
const [token, setToken] = useState("");
|
||||
const [baseURL, setBaseURL] = useState("");
|
||||
const [remoteURL, setRemoteURL] = useState("");
|
||||
const [listenAddr, setListenAddr] = useState("");
|
||||
const [mode, setMode] = useState("");
|
||||
// Platform-specific config values keyed by the adapter Config json tag
|
||||
// (receive_mode/webhook_path/mode/listen_addr/webhook_url/... ). Rendered only
|
||||
// for the currently selected platform's fields.
|
||||
const [platVals, setPlatVals] = useState<Record<string, string>>({});
|
||||
const [allowGroups, setAllowGroups] = useState("");
|
||||
const [denyGroups, setDenyGroups] = useState("");
|
||||
const [allowUsers, setAllowUsers] = useState("");
|
||||
@@ -91,9 +136,7 @@ export function ChannelsModule(): React.ReactNode {
|
||||
setCfgId("");
|
||||
setToken("");
|
||||
setBaseURL("");
|
||||
setRemoteURL("");
|
||||
setListenAddr("");
|
||||
setMode("");
|
||||
setPlatVals({});
|
||||
setAllowGroups("");
|
||||
setDenyGroups("");
|
||||
setAllowUsers("");
|
||||
@@ -110,23 +153,38 @@ 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.
|
||||
const specificMap: Record<string, unknown> = {};
|
||||
for (const f of PLATFORM_FIELDS[cfgPlatform] ?? []) {
|
||||
const v = (platVals[f.key] ?? "").trim();
|
||||
if (v) specificMap[f.key] = v;
|
||||
}
|
||||
let configMap: Record<string, unknown> | undefined;
|
||||
if (extraConfig.trim()) {
|
||||
let extra: unknown;
|
||||
try {
|
||||
configMap = JSON.parse(extraConfig);
|
||||
extra = JSON.parse(extraConfig);
|
||||
} catch {
|
||||
setErr("额外配置 JSON 格式有误");
|
||||
setBusy(null);
|
||||
return;
|
||||
}
|
||||
if (typeof extra !== "object" || extra === null || Array.isArray(extra)) {
|
||||
setErr("额外配置 JSON 需为对象(如 {\"receive_mode\":\"ws\"})");
|
||||
setBusy(null);
|
||||
return;
|
||||
}
|
||||
configMap = { ...specificMap, ...(extra as Record<string, unknown>) };
|
||||
} else if (Object.keys(specificMap).length) {
|
||||
configMap = specificMap;
|
||||
}
|
||||
adminSaveChannelConfig(cfgPlatform, {
|
||||
id: cfgId.trim() || undefined,
|
||||
token: token.trim() || undefined,
|
||||
baseURL: baseURL.trim() || undefined,
|
||||
remoteURL: remoteURL.trim() || undefined,
|
||||
listenAddr: listenAddr.trim() || undefined,
|
||||
mode: mode.trim() || undefined,
|
||||
config: configMap,
|
||||
allowGroups: toList(allowGroups),
|
||||
denyGroups: toList(denyGroups),
|
||||
@@ -142,9 +200,7 @@ export function ChannelsModule(): React.ReactNode {
|
||||
setCfgId("");
|
||||
setToken("");
|
||||
setBaseURL("");
|
||||
setRemoteURL("");
|
||||
setListenAddr("");
|
||||
setMode("");
|
||||
setPlatVals({});
|
||||
setAllowGroups("");
|
||||
setDenyGroups("");
|
||||
setAllowUsers("");
|
||||
@@ -270,24 +326,24 @@ export function ChannelsModule(): React.ReactNode {
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">Token(bot_token)</span>
|
||||
<input value={token} onChange={(e) => setToken(e.target.value)} placeholder="机器人 token" className={inputCls} />
|
||||
<input type="password" value={token} onChange={(e) => setToken(e.target.value)} placeholder="机器人 token" className={inputCls} />
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">baseURL(地址)</span>
|
||||
<input value={baseURL} onChange={(e) => setBaseURL(e.target.value)} placeholder="https://…" className={inputCls} />
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">mode(qq-obv11:client/server)</span>
|
||||
<input value={mode} onChange={(e) => setMode(e.target.value)} placeholder="server" className={inputCls} />
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">remoteURL(qq client)</span>
|
||||
<input value={remoteURL} onChange={(e) => setRemoteURL(e.target.value)} placeholder="ws://…" className={inputCls} />
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">listenAddr(qq server)</span>
|
||||
<input value={listenAddr} onChange={(e) => setListenAddr(e.target.value)} placeholder="0.0.0.0:6700" className={inputCls} />
|
||||
</label>
|
||||
{(PLATFORM_FIELDS[cfgPlatform] ?? []).map((f) => (
|
||||
<label key={f.key} className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">{f.label}</span>
|
||||
<input
|
||||
type={f.secret ? "password" : "text"}
|
||||
value={platVals[f.key] ?? ""}
|
||||
onChange={(e) => setPlatVals((prev) => ({ ...prev, [f.key]: e.target.value }))}
|
||||
placeholder={f.placeholder}
|
||||
className={inputCls}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<label className="block space-y-1">
|
||||
|
||||
Reference in new issue
Block a user