feat: 渠道管理分两块(.env必要回退+删除同步+可用类型/已保存分开)
agent-120 .env只留必要回退: - .env.example KOOK移4可选业务字段(RECEIVE_MODE/WEBHOOK_PATH/VERIFY_TOKEN/ENCRYPT_KEY),只留token/base_url; channelAccountsFromEnv不读这4个; 说明业务字段走channelConfigForm.channels.json - 黑名单/限流/receive_mode等走管理面板热更新, env仅必要连接回退 agent-121 删除渠道列表同步(根因: adminListChannels枚举caps,删除不同步caps): - adminListChannels 以channels.json为准: 遍历caps时已删账号adapter跳过(s.channels注入时), 删除/保存后列表=channels.json, 删除即消失 - (capReg Remove后续可加,范围外标注) agent-122 渠道管理分两块显示(主人:不割裂): - ①可用适配器类型(顶部PLATFORM_TYPES全集grid卡片,已配置/未配置Tag,点新建配置) - ②已保存配置(下方AdminTable过滤accounts>0,账号+bot身份+掩码token提示+连接/断开/删除) - 拆 channelConfigForm.tsx(248行) 表单抽出; Channels.tsx 463→306行; token只回填hint不写值(保存留空不改secret) 全仓 build/vet/test绿+前端build绿(356KB)
This commit is contained in:
1 parent
b614633432
commit
0ed7984de3
8 files changed
+578
-290
No files matched your search
+12
-7
@@ -87,7 +87,11 @@ func buildPlatformAdapters(platform string, accts []config.ChannelAccount, log *
|
||||
// channelAccountsFromEnv synthesizes the env fallback: the single-account
|
||||
// CHANNEL_<NAME>_* adapters plus any CHANNELS_JSON multi-account entries. It is
|
||||
// the .env fallback passed to runtimeconfig.LoadChannels when channels.json is
|
||||
// absent or invalid.
|
||||
// absent or invalid. .env only carries the necessary connection fallback
|
||||
// (token/base_url/...); business and advanced per-account fields (receive_mode,
|
||||
// webhook path/verify_token/encrypt_key, allow/deny lists, rate limits) are
|
||||
// configured in the panel's channel management and live in channels.json, never
|
||||
// in .env.
|
||||
//
|
||||
// A token-requiring platform (kook/telegram/weixin-ilink) is synthesized only
|
||||
// when its token env var is set; with an unset token there is no account at all,
|
||||
@@ -96,16 +100,17 @@ func buildPlatformAdapters(platform string, accts []config.ChannelAccount, log *
|
||||
// gate decides whether to dial) so the channel list stays stable.
|
||||
func channelAccountsFromEnv() map[string][]config.ChannelAccount {
|
||||
out := map[string][]config.ChannelAccount{}
|
||||
// KOOK: only the necessary connection fields. receive_mode / webhook_path /
|
||||
// webhook_verify_token / webhook_encrypt_key are business·advanced fields that
|
||||
// go to channels.json via the panel — .env does not fall back on them.
|
||||
if tok := os.Getenv("CHANNEL_KOOK_TOKEN"); tok != "" {
|
||||
out["kook"] = append(out["kook"], config.ChannelAccount{Config: map[string]any{
|
||||
"bot_token": tok,
|
||||
"base_url": os.Getenv("CHANNEL_KOOK_BASE_URL"),
|
||||
"receive_mode": os.Getenv("CHANNEL_KOOK_RECEIVE_MODE"),
|
||||
"webhook_path": os.Getenv("CHANNEL_KOOK_WEBHOOK_PATH"),
|
||||
"webhook_verify_token": os.Getenv("CHANNEL_KOOK_WEBHOOK_VERIFY_TOKEN"),
|
||||
"webhook_encrypt_key": os.Getenv("CHANNEL_KOOK_WEBHOOK_ENCRYPT_KEY"),
|
||||
"bot_token": tok,
|
||||
"base_url": os.Getenv("CHANNEL_KOOK_BASE_URL"),
|
||||
}})
|
||||
}
|
||||
// Telegram: token (necessary) + optional webhook_url (connection). Business
|
||||
// tuning goes to channels.json.
|
||||
if tok := os.Getenv("CHANNEL_TELEGRAM_BOT_TOKEN"); tok != "" {
|
||||
out["telegram"] = append(out["telegram"], config.ChannelAccount{Config: map[string]any{
|
||||
"bot_token": tok,
|
||||
|
||||
+26
-13
@@ -151,22 +151,29 @@ func TestChannelFactoryBuildFromFile(t *testing.T) {
|
||||
|
||||
// TestNamedAdapterForwardsWebhook verifies the production wrapper forwards a
|
||||
// kook webhook-mode adapter's InboundHandler/WebhookPath to the channel bridge,
|
||||
// so bridge.WebhookHandlers() mounts the KOOK callback route on the gateway.
|
||||
// so bridge.WebhookHandlers() mounts the KOOK callback route on the gateway. The
|
||||
// webhook business fields (receive_mode/webhook_path) are sourced from
|
||||
// channels.json — .env only carries the necessary connection fallback.
|
||||
func TestNamedAdapterForwardsWebhook(t *testing.T) {
|
||||
t.Setenv("CHANNEL_KOOK_TOKEN", "kook-secret")
|
||||
t.Setenv("CHANNEL_KOOK_RECEIVE_MODE", "webhook")
|
||||
t.Setenv("CHANNEL_KOOK_WEBHOOK_PATH", "/webhook/kook")
|
||||
|
||||
adapters := buildChannelAdapters(testFactory(t))
|
||||
path := filepath.Join(t.TempDir(), "channels.json")
|
||||
raw := `{"kook":[{"id":"conf0","config":{"bot_token":"kook-secret","receive_mode":"webhook","webhook_path":"/webhook/kook"}}]}`
|
||||
if err := os.WriteFile(path, []byte(raw), 0o600); err != nil {
|
||||
t.Fatalf("write channels.json: %v", err)
|
||||
}
|
||||
f := &channelFactory{path: path, fallback: channelAccountsFromEnv(), log: testLog()}
|
||||
adpts, err := f.Build("kook")
|
||||
if err != nil {
|
||||
t.Fatalf("Build: %v", err)
|
||||
}
|
||||
var found *namedAdapter
|
||||
for _, a := range adapters {
|
||||
for _, a := range adpts {
|
||||
if nwa, ok := a.(*namedAdapter); ok && barePlatform(nwa.Name()) == "kook" {
|
||||
found = nwa
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatalf("kook named adapter not found, got %v", names(adapters))
|
||||
t.Fatalf("kook named adapter not found, got %v", names(adpts))
|
||||
}
|
||||
if p := found.WebhookPath(); p != "/webhook/kook" {
|
||||
t.Errorf("WebhookPath = %q, want /webhook/kook", p)
|
||||
@@ -179,11 +186,17 @@ func TestNamedAdapterForwardsWebhook(t *testing.T) {
|
||||
// TestNamedAdapterNoWebhookInWSMode verifies a ws-mode adapter (all non-webhook
|
||||
// platforms) does not surface a webhook handler through the wrapper.
|
||||
func TestNamedAdapterNoWebhookInWSMode(t *testing.T) {
|
||||
t.Setenv("CHANNEL_KOOK_TOKEN", "kook-secret")
|
||||
t.Setenv("CHANNEL_KOOK_RECEIVE_MODE", "ws")
|
||||
|
||||
adapters := buildChannelAdapters(testFactory(t))
|
||||
for _, a := range adapters {
|
||||
path := filepath.Join(t.TempDir(), "channels.json")
|
||||
raw := `{"kook":[{"id":"conf0","config":{"bot_token":"kook-secret","receive_mode":"ws"}}]}`
|
||||
if err := os.WriteFile(path, []byte(raw), 0o600); err != nil {
|
||||
t.Fatalf("write channels.json: %v", err)
|
||||
}
|
||||
f := &channelFactory{path: path, fallback: channelAccountsFromEnv(), log: testLog()}
|
||||
adpts, err := f.Build("kook")
|
||||
if err != nil {
|
||||
t.Fatalf("Build: %v", err)
|
||||
}
|
||||
for _, a := range adpts {
|
||||
if barePlatform(a.Name()) != "kook" {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user