Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
711a7e34
authored
Aug 04, 2025
by
Calcium-Ion
Committed by
GitHub
Aug 04, 2025
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1498 from QuantumNous/multi-key-manage
feat: add multi-key management
parents
bb08de0b
808c8c5b
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1099 additions
and
60 deletions
+1099
-60
controller/channel.go
+425
-0
model/channel.go
+22
-11
model/channel_cache.go
+1
-1
router/api-router.go
+1
-0
web/src/components/table/channels/ChannelsColumnDefs.js
+47
-48
web/src/components/table/channels/ChannelsTable.jsx
+7
-0
web/src/components/table/channels/index.jsx
+7
-0
web/src/components/table/channels/modals/MultiKeyManageModal.jsx
+579
-0
web/src/hooks/channels/useChannelsData.js
+10
-0
No files found.
controller/channel.go
View file @
711a7e34
...
...
@@ -71,6 +71,13 @@ func parseStatusFilter(statusParam string) int {
}
}
func
clearChannelInfo
(
channel
*
model
.
Channel
)
{
if
channel
.
ChannelInfo
.
IsMultiKey
{
channel
.
ChannelInfo
.
MultiKeyDisabledReason
=
nil
channel
.
ChannelInfo
.
MultiKeyDisabledTime
=
nil
}
}
func
GetAllChannels
(
c
*
gin
.
Context
)
{
pageInfo
:=
common
.
GetPageQuery
(
c
)
channelData
:=
make
([]
*
model
.
Channel
,
0
)
...
...
@@ -145,6 +152,10 @@ func GetAllChannels(c *gin.Context) {
}
}
for
_
,
datum
:=
range
channelData
{
clearChannelInfo
(
datum
)
}
countQuery
:=
model
.
DB
.
Model
(
&
model
.
Channel
{})
if
statusFilter
==
common
.
ChannelStatusEnabled
{
countQuery
=
countQuery
.
Where
(
"status = ?"
,
common
.
ChannelStatusEnabled
)
...
...
@@ -371,6 +382,10 @@ func SearchChannels(c *gin.Context) {
pagedData
:=
channelData
[
startIdx
:
endIdx
]
for
_
,
datum
:=
range
pagedData
{
clearChannelInfo
(
datum
)
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
...
...
@@ -394,6 +409,9 @@ func GetChannel(c *gin.Context) {
common
.
ApiError
(
c
,
err
)
return
}
if
channel
!=
nil
{
clearChannelInfo
(
channel
)
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
...
...
@@ -827,6 +845,7 @@ func UpdateChannel(c *gin.Context) {
}
model
.
InitChannelCache
()
channel
.
Key
=
""
clearChannelInfo
(
&
channel
.
Channel
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
...
...
@@ -1030,3 +1049,409 @@ func CopyChannel(c *gin.Context) {
// success
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
gin
.
H
{
"id"
:
clone
.
Id
}})
}
// MultiKeyManageRequest represents the request for multi-key management operations
type
MultiKeyManageRequest
struct
{
ChannelId
int
`json:"channel_id"`
Action
string
`json:"action"`
// "disable_key", "enable_key", "delete_disabled_keys", "get_key_status"
KeyIndex
*
int
`json:"key_index,omitempty"`
// for disable_key and enable_key actions
Page
int
`json:"page,omitempty"`
// for get_key_status pagination
PageSize
int
`json:"page_size,omitempty"`
// for get_key_status pagination
Status
*
int
`json:"status,omitempty"`
// for get_key_status filtering: 1=enabled, 2=manual_disabled, 3=auto_disabled, nil=all
}
// MultiKeyStatusResponse represents the response for key status query
type
MultiKeyStatusResponse
struct
{
Keys
[]
KeyStatus
`json:"keys"`
Total
int
`json:"total"`
Page
int
`json:"page"`
PageSize
int
`json:"page_size"`
TotalPages
int
`json:"total_pages"`
// Statistics
EnabledCount
int
`json:"enabled_count"`
ManualDisabledCount
int
`json:"manual_disabled_count"`
AutoDisabledCount
int
`json:"auto_disabled_count"`
}
type
KeyStatus
struct
{
Index
int
`json:"index"`
Status
int
`json:"status"`
// 1: enabled, 2: disabled
DisabledTime
int64
`json:"disabled_time,omitempty"`
Reason
string
`json:"reason,omitempty"`
KeyPreview
string
`json:"key_preview"`
// first 10 chars of key for identification
}
// ManageMultiKeys handles multi-key management operations
func
ManageMultiKeys
(
c
*
gin
.
Context
)
{
request
:=
MultiKeyManageRequest
{}
err
:=
c
.
ShouldBindJSON
(
&
request
)
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
channel
,
err
:=
model
.
GetChannelById
(
request
.
ChannelId
,
true
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"渠道不存在"
,
})
return
}
if
!
channel
.
ChannelInfo
.
IsMultiKey
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"该渠道不是多密钥模式"
,
})
return
}
switch
request
.
Action
{
case
"get_key_status"
:
keys
:=
channel
.
GetKeys
()
// Default pagination parameters
page
:=
request
.
Page
pageSize
:=
request
.
PageSize
if
page
<=
0
{
page
=
1
}
if
pageSize
<=
0
{
pageSize
=
50
// Default page size
}
// Statistics for all keys (unchanged by filtering)
var
enabledCount
,
manualDisabledCount
,
autoDisabledCount
int
// Build all key status data first
var
allKeyStatusList
[]
KeyStatus
for
i
,
key
:=
range
keys
{
status
:=
1
// default enabled
var
disabledTime
int64
var
reason
string
if
channel
.
ChannelInfo
.
MultiKeyStatusList
!=
nil
{
if
s
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyStatusList
[
i
];
exists
{
status
=
s
}
}
// Count for statistics (all keys)
switch
status
{
case
1
:
enabledCount
++
case
2
:
manualDisabledCount
++
case
3
:
autoDisabledCount
++
}
if
status
!=
1
{
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
!=
nil
{
disabledTime
=
channel
.
ChannelInfo
.
MultiKeyDisabledTime
[
i
]
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
!=
nil
{
reason
=
channel
.
ChannelInfo
.
MultiKeyDisabledReason
[
i
]
}
}
// Create key preview (first 10 chars)
keyPreview
:=
key
if
len
(
key
)
>
10
{
keyPreview
=
key
[
:
10
]
+
"..."
}
allKeyStatusList
=
append
(
allKeyStatusList
,
KeyStatus
{
Index
:
i
,
Status
:
status
,
DisabledTime
:
disabledTime
,
Reason
:
reason
,
KeyPreview
:
keyPreview
,
})
}
// Apply status filter if specified
var
filteredKeyStatusList
[]
KeyStatus
if
request
.
Status
!=
nil
{
for
_
,
keyStatus
:=
range
allKeyStatusList
{
if
keyStatus
.
Status
==
*
request
.
Status
{
filteredKeyStatusList
=
append
(
filteredKeyStatusList
,
keyStatus
)
}
}
}
else
{
filteredKeyStatusList
=
allKeyStatusList
}
// Calculate pagination based on filtered results
filteredTotal
:=
len
(
filteredKeyStatusList
)
totalPages
:=
(
filteredTotal
+
pageSize
-
1
)
/
pageSize
if
totalPages
==
0
{
totalPages
=
1
}
if
page
>
totalPages
{
page
=
totalPages
}
// Calculate range for current page
start
:=
(
page
-
1
)
*
pageSize
end
:=
start
+
pageSize
if
end
>
filteredTotal
{
end
=
filteredTotal
}
// Get the page data
var
pageKeyStatusList
[]
KeyStatus
if
start
<
filteredTotal
{
pageKeyStatusList
=
filteredKeyStatusList
[
start
:
end
]
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
MultiKeyStatusResponse
{
Keys
:
pageKeyStatusList
,
Total
:
filteredTotal
,
// Total of filtered results
Page
:
page
,
PageSize
:
pageSize
,
TotalPages
:
totalPages
,
EnabledCount
:
enabledCount
,
// Overall statistics
ManualDisabledCount
:
manualDisabledCount
,
// Overall statistics
AutoDisabledCount
:
autoDisabledCount
,
// Overall statistics
},
})
return
case
"disable_key"
:
if
request
.
KeyIndex
==
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"未指定要禁用的密钥索引"
,
})
return
}
keyIndex
:=
*
request
.
KeyIndex
if
keyIndex
<
0
||
keyIndex
>=
channel
.
ChannelInfo
.
MultiKeySize
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"密钥索引超出范围"
,
})
return
}
if
channel
.
ChannelInfo
.
MultiKeyStatusList
==
nil
{
channel
.
ChannelInfo
.
MultiKeyStatusList
=
make
(
map
[
int
]
int
)
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
==
nil
{
channel
.
ChannelInfo
.
MultiKeyDisabledTime
=
make
(
map
[
int
]
int64
)
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
==
nil
{
channel
.
ChannelInfo
.
MultiKeyDisabledReason
=
make
(
map
[
int
]
string
)
}
channel
.
ChannelInfo
.
MultiKeyStatusList
[
keyIndex
]
=
2
// disabled
err
=
channel
.
Update
()
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
model
.
InitChannelCache
()
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
"密钥已禁用"
,
})
return
case
"enable_key"
:
if
request
.
KeyIndex
==
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"未指定要启用的密钥索引"
,
})
return
}
keyIndex
:=
*
request
.
KeyIndex
if
keyIndex
<
0
||
keyIndex
>=
channel
.
ChannelInfo
.
MultiKeySize
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"密钥索引超出范围"
,
})
return
}
// 从状态列表中删除该密钥的记录,使其回到默认启用状态
if
channel
.
ChannelInfo
.
MultiKeyStatusList
!=
nil
{
delete
(
channel
.
ChannelInfo
.
MultiKeyStatusList
,
keyIndex
)
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
!=
nil
{
delete
(
channel
.
ChannelInfo
.
MultiKeyDisabledTime
,
keyIndex
)
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
!=
nil
{
delete
(
channel
.
ChannelInfo
.
MultiKeyDisabledReason
,
keyIndex
)
}
err
=
channel
.
Update
()
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
model
.
InitChannelCache
()
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
"密钥已启用"
,
})
return
case
"enable_all_keys"
:
// 清空所有禁用状态,使所有密钥回到默认启用状态
var
enabledCount
int
if
channel
.
ChannelInfo
.
MultiKeyStatusList
!=
nil
{
enabledCount
=
len
(
channel
.
ChannelInfo
.
MultiKeyStatusList
)
}
channel
.
ChannelInfo
.
MultiKeyStatusList
=
make
(
map
[
int
]
int
)
channel
.
ChannelInfo
.
MultiKeyDisabledTime
=
make
(
map
[
int
]
int64
)
channel
.
ChannelInfo
.
MultiKeyDisabledReason
=
make
(
map
[
int
]
string
)
err
=
channel
.
Update
()
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
model
.
InitChannelCache
()
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
fmt
.
Sprintf
(
"已启用 %d 个密钥"
,
enabledCount
),
})
return
case
"disable_all_keys"
:
// 禁用所有启用的密钥
if
channel
.
ChannelInfo
.
MultiKeyStatusList
==
nil
{
channel
.
ChannelInfo
.
MultiKeyStatusList
=
make
(
map
[
int
]
int
)
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
==
nil
{
channel
.
ChannelInfo
.
MultiKeyDisabledTime
=
make
(
map
[
int
]
int64
)
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
==
nil
{
channel
.
ChannelInfo
.
MultiKeyDisabledReason
=
make
(
map
[
int
]
string
)
}
var
disabledCount
int
for
i
:=
0
;
i
<
channel
.
ChannelInfo
.
MultiKeySize
;
i
++
{
status
:=
1
// default enabled
if
s
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyStatusList
[
i
];
exists
{
status
=
s
}
// 只禁用当前启用的密钥
if
status
==
1
{
channel
.
ChannelInfo
.
MultiKeyStatusList
[
i
]
=
2
// disabled
disabledCount
++
}
}
if
disabledCount
==
0
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"没有可禁用的密钥"
,
})
return
}
err
=
channel
.
Update
()
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
model
.
InitChannelCache
()
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
fmt
.
Sprintf
(
"已禁用 %d 个密钥"
,
disabledCount
),
})
return
case
"delete_disabled_keys"
:
keys
:=
channel
.
GetKeys
()
var
remainingKeys
[]
string
var
deletedCount
int
var
newStatusList
=
make
(
map
[
int
]
int
)
var
newDisabledTime
=
make
(
map
[
int
]
int64
)
var
newDisabledReason
=
make
(
map
[
int
]
string
)
newIndex
:=
0
for
i
,
key
:=
range
keys
{
status
:=
1
// default enabled
if
channel
.
ChannelInfo
.
MultiKeyStatusList
!=
nil
{
if
s
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyStatusList
[
i
];
exists
{
status
=
s
}
}
// 只删除自动禁用(status == 3)的密钥,保留启用(status == 1)和手动禁用(status == 2)的密钥
if
status
==
3
{
deletedCount
++
}
else
{
remainingKeys
=
append
(
remainingKeys
,
key
)
// 保留非自动禁用密钥的状态信息,重新索引
if
status
!=
1
{
newStatusList
[
newIndex
]
=
status
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
!=
nil
{
if
t
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyDisabledTime
[
i
];
exists
{
newDisabledTime
[
newIndex
]
=
t
}
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
!=
nil
{
if
r
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyDisabledReason
[
i
];
exists
{
newDisabledReason
[
newIndex
]
=
r
}
}
}
newIndex
++
}
}
if
deletedCount
==
0
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"没有需要删除的自动禁用密钥"
,
})
return
}
// Update channel with remaining keys
channel
.
Key
=
strings
.
Join
(
remainingKeys
,
"
\n
"
)
channel
.
ChannelInfo
.
MultiKeySize
=
len
(
remainingKeys
)
channel
.
ChannelInfo
.
MultiKeyStatusList
=
newStatusList
channel
.
ChannelInfo
.
MultiKeyDisabledTime
=
newDisabledTime
channel
.
ChannelInfo
.
MultiKeyDisabledReason
=
newDisabledReason
err
=
channel
.
Update
()
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
model
.
InitChannelCache
()
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
fmt
.
Sprintf
(
"已删除 %d 个自动禁用的密钥"
,
deletedCount
),
"data"
:
deletedCount
,
})
return
default
:
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"不支持的操作"
,
})
return
}
}
model/channel.go
View file @
711a7e34
...
...
@@ -41,6 +41,7 @@ type Channel struct {
Priority
*
int64
`json:"priority" gorm:"bigint;default:0"`
AutoBan
*
int
`json:"auto_ban" gorm:"default:1"`
OtherInfo
string
`json:"other_info"`
Settings
string
`json:"settings"`
Tag
*
string
`json:"tag" gorm:"index"`
Setting
*
string
`json:"setting" gorm:"type:text"`
// 渠道额外设置
ParamOverride
*
string
`json:"param_override" gorm:"type:text"`
...
...
@@ -52,11 +53,13 @@ type Channel struct {
}
type
ChannelInfo
struct
{
IsMultiKey
bool
`json:"is_multi_key"`
// 是否多Key模式
MultiKeySize
int
`json:"multi_key_size"`
// 多Key模式下的Key数量
MultiKeyStatusList
map
[
int
]
int
`json:"multi_key_status_list"`
// key状态列表,key index -> status
MultiKeyPollingIndex
int
`json:"multi_key_polling_index"`
// 多Key模式下轮询的key索引
MultiKeyMode
constant
.
MultiKeyMode
`json:"multi_key_mode"`
IsMultiKey
bool
`json:"is_multi_key"`
// 是否多Key模式
MultiKeySize
int
`json:"multi_key_size"`
// 多Key模式下的Key数量
MultiKeyStatusList
map
[
int
]
int
`json:"multi_key_status_list"`
// key状态列表,key index -> status
MultiKeyDisabledReason
map
[
int
]
string
`json:"multi_key_disabled_reason,omitempty"`
// key禁用原因列表,key index -> reason
MultiKeyDisabledTime
map
[
int
]
int64
`json:"multi_key_disabled_time,omitempty"`
// key禁用时间列表,key index -> time
MultiKeyPollingIndex
int
`json:"multi_key_polling_index"`
// 多Key模式下轮询的key索引
MultiKeyMode
constant
.
MultiKeyMode
`json:"multi_key_mode"`
}
// Value implements driver.Valuer interface
...
...
@@ -70,7 +73,7 @@ func (c *ChannelInfo) Scan(value interface{}) error {
return
common
.
Unmarshal
(
bytesValue
,
c
)
}
func
(
channel
*
Channel
)
g
etKeys
()
[]
string
{
func
(
channel
*
Channel
)
G
etKeys
()
[]
string
{
if
channel
.
Key
==
""
{
return
[]
string
{}
}
...
...
@@ -101,7 +104,7 @@ func (channel *Channel) GetNextEnabledKey() (string, int, *types.NewAPIError) {
}
// Obtain all keys (split by \n)
keys
:=
channel
.
g
etKeys
()
keys
:=
channel
.
G
etKeys
()
if
len
(
keys
)
==
0
{
// No keys available, return error, should disable the channel
return
""
,
0
,
types
.
NewError
(
errors
.
New
(
"no keys available"
),
types
.
ErrorCodeChannelNoAvailableKey
)
...
...
@@ -528,8 +531,8 @@ func CleanupChannelPollingLocks() {
})
}
func
handlerMultiKeyUpdate
(
channel
*
Channel
,
usingKey
string
,
status
int
)
{
keys
:=
channel
.
g
etKeys
()
func
handlerMultiKeyUpdate
(
channel
*
Channel
,
usingKey
string
,
status
int
,
reason
string
)
{
keys
:=
channel
.
G
etKeys
()
if
len
(
keys
)
==
0
{
channel
.
Status
=
status
}
else
{
...
...
@@ -547,6 +550,14 @@ func handlerMultiKeyUpdate(channel *Channel, usingKey string, status int) {
delete
(
channel
.
ChannelInfo
.
MultiKeyStatusList
,
keyIndex
)
}
else
{
channel
.
ChannelInfo
.
MultiKeyStatusList
[
keyIndex
]
=
status
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
==
nil
{
channel
.
ChannelInfo
.
MultiKeyDisabledReason
=
make
(
map
[
int
]
string
)
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
==
nil
{
channel
.
ChannelInfo
.
MultiKeyDisabledTime
=
make
(
map
[
int
]
int64
)
}
channel
.
ChannelInfo
.
MultiKeyDisabledReason
[
keyIndex
]
=
reason
channel
.
ChannelInfo
.
MultiKeyDisabledTime
[
keyIndex
]
=
common
.
GetTimestamp
()
}
if
len
(
channel
.
ChannelInfo
.
MultiKeyStatusList
)
>=
channel
.
ChannelInfo
.
MultiKeySize
{
channel
.
Status
=
common
.
ChannelStatusAutoDisabled
...
...
@@ -569,7 +580,7 @@ func UpdateChannelStatus(channelId int, usingKey string, status int, reason stri
}
if
channelCache
.
ChannelInfo
.
IsMultiKey
{
// 如果是多Key模式,更新缓存中的状态
handlerMultiKeyUpdate
(
channelCache
,
usingKey
,
status
)
handlerMultiKeyUpdate
(
channelCache
,
usingKey
,
status
,
reason
)
//CacheUpdateChannel(channelCache)
//return true
}
else
{
...
...
@@ -600,7 +611,7 @@ func UpdateChannelStatus(channelId int, usingKey string, status int, reason stri
if
channel
.
ChannelInfo
.
IsMultiKey
{
beforeStatus
:=
channel
.
Status
handlerMultiKeyUpdate
(
channel
,
usingKey
,
status
)
handlerMultiKeyUpdate
(
channel
,
usingKey
,
status
,
reason
)
if
beforeStatus
!=
channel
.
Status
{
shouldUpdateAbilities
=
true
}
...
...
model/channel_cache.go
View file @
711a7e34
...
...
@@ -70,7 +70,7 @@ func InitChannelCache() {
//channelsIDM = newChannelId2channel
for
i
,
channel
:=
range
newChannelId2channel
{
if
channel
.
ChannelInfo
.
IsMultiKey
{
channel
.
Keys
=
channel
.
g
etKeys
()
channel
.
Keys
=
channel
.
G
etKeys
()
if
channel
.
ChannelInfo
.
MultiKeyMode
==
constant
.
MultiKeyModePolling
{
if
oldChannel
,
ok
:=
channelsIDM
[
i
];
ok
{
// 存在旧的渠道,如果是多key且轮询,保留轮询索引信息
...
...
router/api-router.go
View file @
711a7e34
...
...
@@ -120,6 +120,7 @@ func SetApiRouter(router *gin.Engine) {
channelRoute
.
POST
(
"/batch/tag"
,
controller
.
BatchSetChannelTag
)
channelRoute
.
GET
(
"/tag/models"
,
controller
.
GetTagModels
)
channelRoute
.
POST
(
"/copy/:id"
,
controller
.
CopyChannel
)
channelRoute
.
POST
(
"/multi_key/manage"
,
controller
.
ManageMultiKeys
)
}
tokenRoute
:=
apiRouter
.
Group
(
"/token"
)
tokenRoute
.
Use
(
middleware
.
UserAuth
())
...
...
web/src/components/table/channels/ChannelsColumnDefs.js
View file @
711a7e34
...
...
@@ -210,7 +210,9 @@ export const getChannelsColumns = ({
copySelectedChannel
,
refresh
,
activePage
,
channels
channels
,
setShowMultiKeyManageModal
,
setCurrentMultiKeyChannel
})
=>
{
return
[
{
...
...
@@ -503,36 +505,50 @@ export const getChannelsColumns = ({
/
>
<
/SplitButtonGroup
>
{
record
.
status
===
1
?
(
<
Button
type
=
'danger'
size
=
"small"
onClick
=
{()
=>
manageChannel
(
record
.
id
,
'disable'
,
record
)}
>
{
t
(
'禁用'
)}
<
/Button
>
)
:
(
<
Button
size
=
"small"
onClick
=
{()
=>
manageChannel
(
record
.
id
,
'enable'
,
record
)}
>
{
t
(
'启用'
)}
<
/Button
>
)
}
{
record
.
channel_info
?.
is_multi_key
?
(
<
SplitButtonGroup
aria
-
label
=
{
t
(
'多密钥渠道操作项目组'
)}
>
{
record
.
status
===
1
?
(
<
Button
type
=
'danger'
size
=
"small"
onClick
=
{()
=>
manageChannel
(
record
.
id
,
'disable'
,
record
)}
>
{
t
(
'禁用'
)}
<
/Button
>
)
:
(
<
Button
size
=
"small"
onClick
=
{()
=>
manageChannel
(
record
.
id
,
'enable'
,
record
)}
>
{
t
(
'启用'
)}
<
/Button
>
)
}
<
Button
type
=
'tertiary'
size
=
"small"
onClick
=
{()
=>
{
setEditingChannel
(
record
);
setShowEdit
(
true
);
}}
>
{
t
(
'编辑'
)}
<
/Button
>
<
Dropdown
trigger
=
'click'
position
=
'bottomRight'
menu
=
{[
{
node
:
'item'
,
name
:
t
(
'启用全部密钥'
),
onClick
:
()
=>
manageChannel
(
record
.
id
,
'enable_all'
,
record
),
name
:
t
(
'多key管理'
),
onClick
:
()
=>
{
setCurrentMultiKeyChannel
(
record
);
setShowMultiKeyManageModal
(
true
);
},
}
]}
>
...
...
@@ -544,35 +560,18 @@ export const getChannelsColumns = ({
<
/Dropdown
>
<
/SplitButtonGroup
>
)
:
(
record
.
status
===
1
?
(
<
Button
type
=
'danger'
size
=
"small"
onClick
=
{()
=>
manageChannel
(
record
.
id
,
'disable'
,
record
)}
>
{
t
(
'禁用'
)}
<
/Button
>
)
:
(
<
Button
size
=
"small"
onClick
=
{()
=>
manageChannel
(
record
.
id
,
'enable'
,
record
)}
>
{
t
(
'启用'
)}
<
/Button
>
)
<
Button
type
=
'tertiary'
size
=
"small"
onClick
=
{()
=>
{
setEditingChannel
(
record
);
setShowEdit
(
true
);
}}
>
{
t
(
'编辑'
)}
<
/Button
>
)}
<
Button
type
=
'tertiary'
size
=
"small"
onClick
=
{()
=>
{
setEditingChannel
(
record
);
setShowEdit
(
true
);
}}
>
{
t
(
'编辑'
)}
<
/Button
>
<
Dropdown
trigger
=
'click'
position
=
'bottomRight'
...
...
web/src/components/table/channels/ChannelsTable.jsx
View file @
711a7e34
...
...
@@ -57,6 +57,9 @@ const ChannelsTable = (channelsData) => {
setEditingTag
,
copySelectedChannel
,
refresh
,
// Multi-key management
setShowMultiKeyManageModal
,
setCurrentMultiKeyChannel
,
}
=
channelsData
;
// Get all columns
...
...
@@ -79,6 +82,8 @@ const ChannelsTable = (channelsData) => {
refresh
,
activePage
,
channels
,
setShowMultiKeyManageModal
,
setCurrentMultiKeyChannel
,
});
},
[
t
,
...
...
@@ -98,6 +103,8 @@ const ChannelsTable = (channelsData) => {
refresh
,
activePage
,
channels
,
setShowMultiKeyManageModal
,
setCurrentMultiKeyChannel
,
]);
// Filter columns based on visibility settings
...
...
web/src/components/table/channels/index.jsx
View file @
711a7e34
...
...
@@ -30,6 +30,7 @@ import ModelTestModal from './modals/ModelTestModal.jsx';
import
ColumnSelectorModal
from
'./modals/ColumnSelectorModal.jsx'
;
import
EditChannelModal
from
'./modals/EditChannelModal.jsx'
;
import
EditTagModal
from
'./modals/EditTagModal.jsx'
;
import
MultiKeyManageModal
from
'./modals/MultiKeyManageModal.jsx'
;
import
{
createCardProPagination
}
from
'../../../helpers/utils'
;
const
ChannelsPage
=
()
=>
{
...
...
@@ -54,6 +55,12 @@ const ChannelsPage = () => {
/>
<
BatchTagModal
{
...
channelsData
}
/>
<
ModelTestModal
{
...
channelsData
}
/>
<
MultiKeyManageModal
visible=
{
channelsData
.
showMultiKeyManageModal
}
onCancel=
{
()
=>
channelsData
.
setShowMultiKeyManageModal
(
false
)
}
channel=
{
channelsData
.
currentMultiKeyChannel
}
onRefresh=
{
channelsData
.
refresh
}
/>
{
/* Main Content */
}
<
CardPro
...
...
web/src/components/table/channels/modals/MultiKeyManageModal.jsx
0 → 100644
View file @
711a7e34
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
,
{
useState
,
useEffect
}
from
'react'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
Modal
,
Button
,
Table
,
Tag
,
Typography
,
Space
,
Tooltip
,
Popconfirm
,
Empty
,
Spin
,
Banner
,
Select
,
Pagination
}
from
'@douyinfe/semi-ui'
;
import
{
IconRefresh
,
IconDelete
,
IconClose
,
IconSave
,
IconSetting
}
from
'@douyinfe/semi-icons'
;
import
{
API
,
showError
,
showSuccess
,
timestamp2string
}
from
'../../../../helpers/index.js'
;
const
{
Text
,
Title
}
=
Typography
;
const
MultiKeyManageModal
=
({
visible
,
onCancel
,
channel
,
onRefresh
})
=>
{
const
{
t
}
=
useTranslation
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
keyStatusList
,
setKeyStatusList
]
=
useState
([]);
const
[
operationLoading
,
setOperationLoading
]
=
useState
({});
// Pagination states
const
[
currentPage
,
setCurrentPage
]
=
useState
(
1
);
const
[
pageSize
,
setPageSize
]
=
useState
(
50
);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
totalPages
,
setTotalPages
]
=
useState
(
0
);
// Statistics states
const
[
enabledCount
,
setEnabledCount
]
=
useState
(
0
);
const
[
manualDisabledCount
,
setManualDisabledCount
]
=
useState
(
0
);
const
[
autoDisabledCount
,
setAutoDisabledCount
]
=
useState
(
0
);
// Filter states
const
[
statusFilter
,
setStatusFilter
]
=
useState
(
null
);
// null=all, 1=enabled, 2=manual_disabled, 3=auto_disabled
// Load key status data
const
loadKeyStatus
=
async
(
page
=
currentPage
,
size
=
pageSize
,
status
=
statusFilter
)
=>
{
if
(
!
channel
?.
id
)
return
;
setLoading
(
true
);
try
{
const
requestData
=
{
channel_id
:
channel
.
id
,
action
:
'get_key_status'
,
page
:
page
,
page_size
:
size
};
// Add status filter if specified
if
(
status
!==
null
)
{
requestData
.
status
=
status
;
}
const
res
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
requestData
);
if
(
res
.
data
.
success
)
{
const
data
=
res
.
data
.
data
;
setKeyStatusList
(
data
.
keys
||
[]);
setTotal
(
data
.
total
||
0
);
setCurrentPage
(
data
.
page
||
1
);
setPageSize
(
data
.
page_size
||
50
);
setTotalPages
(
data
.
total_pages
||
0
);
// Update statistics (these are always the overall statistics)
setEnabledCount
(
data
.
enabled_count
||
0
);
setManualDisabledCount
(
data
.
manual_disabled_count
||
0
);
setAutoDisabledCount
(
data
.
auto_disabled_count
||
0
);
}
else
{
showError
(
res
.
data
.
message
);
}
}
catch
(
error
)
{
console
.
error
(
error
);
showError
(
t
(
'获取密钥状态失败'
));
}
finally
{
setLoading
(
false
);
}
};
// Disable a specific key
const
handleDisableKey
=
async
(
keyIndex
)
=>
{
const
operationId
=
`disable_
${
keyIndex
}
`
;
setOperationLoading
(
prev
=>
({
...
prev
,
[
operationId
]:
true
}));
try
{
const
res
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
{
channel_id
:
channel
.
id
,
action
:
'disable_key'
,
key_index
:
keyIndex
});
if
(
res
.
data
.
success
)
{
showSuccess
(
t
(
'密钥已禁用'
));
await
loadKeyStatus
(
currentPage
,
pageSize
);
// Reload current page
onRefresh
&&
onRefresh
();
// Refresh parent component
}
else
{
showError
(
res
.
data
.
message
);
}
}
catch
(
error
)
{
showError
(
t
(
'禁用密钥失败'
));
}
finally
{
setOperationLoading
(
prev
=>
({
...
prev
,
[
operationId
]:
false
}));
}
};
// Enable a specific key
const
handleEnableKey
=
async
(
keyIndex
)
=>
{
const
operationId
=
`enable_
${
keyIndex
}
`
;
setOperationLoading
(
prev
=>
({
...
prev
,
[
operationId
]:
true
}));
try
{
const
res
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
{
channel_id
:
channel
.
id
,
action
:
'enable_key'
,
key_index
:
keyIndex
});
if
(
res
.
data
.
success
)
{
showSuccess
(
t
(
'密钥已启用'
));
await
loadKeyStatus
(
currentPage
,
pageSize
);
// Reload current page
onRefresh
&&
onRefresh
();
// Refresh parent component
}
else
{
showError
(
res
.
data
.
message
);
}
}
catch
(
error
)
{
showError
(
t
(
'启用密钥失败'
));
}
finally
{
setOperationLoading
(
prev
=>
({
...
prev
,
[
operationId
]:
false
}));
}
};
// Enable all disabled keys
const
handleEnableAll
=
async
()
=>
{
setOperationLoading
(
prev
=>
({
...
prev
,
enable_all
:
true
}));
try
{
const
res
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
{
channel_id
:
channel
.
id
,
action
:
'enable_all_keys'
});
if
(
res
.
data
.
success
)
{
showSuccess
(
res
.
data
.
message
||
t
(
'已启用所有密钥'
));
// Reset to first page after bulk operation
setCurrentPage
(
1
);
await
loadKeyStatus
(
1
,
pageSize
);
onRefresh
&&
onRefresh
();
// Refresh parent component
}
else
{
showError
(
res
.
data
.
message
);
}
}
catch
(
error
)
{
showError
(
t
(
'启用所有密钥失败'
));
}
finally
{
setOperationLoading
(
prev
=>
({
...
prev
,
enable_all
:
false
}));
}
};
// Disable all enabled keys
const
handleDisableAll
=
async
()
=>
{
setOperationLoading
(
prev
=>
({
...
prev
,
disable_all
:
true
}));
try
{
const
res
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
{
channel_id
:
channel
.
id
,
action
:
'disable_all_keys'
});
if
(
res
.
data
.
success
)
{
showSuccess
(
res
.
data
.
message
||
t
(
'已禁用所有密钥'
));
// Reset to first page after bulk operation
setCurrentPage
(
1
);
await
loadKeyStatus
(
1
,
pageSize
);
onRefresh
&&
onRefresh
();
// Refresh parent component
}
else
{
showError
(
res
.
data
.
message
);
}
}
catch
(
error
)
{
showError
(
t
(
'禁用所有密钥失败'
));
}
finally
{
setOperationLoading
(
prev
=>
({
...
prev
,
disable_all
:
false
}));
}
};
// Delete all disabled keys
const
handleDeleteDisabledKeys
=
async
()
=>
{
setOperationLoading
(
prev
=>
({
...
prev
,
delete_disabled
:
true
}));
try
{
const
res
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
{
channel_id
:
channel
.
id
,
action
:
'delete_disabled_keys'
});
if
(
res
.
data
.
success
)
{
showSuccess
(
res
.
data
.
message
);
// Reset to first page after deletion as data structure might change
setCurrentPage
(
1
);
await
loadKeyStatus
(
1
,
pageSize
);
onRefresh
&&
onRefresh
();
// Refresh parent component
}
else
{
showError
(
res
.
data
.
message
);
}
}
catch
(
error
)
{
showError
(
t
(
'删除禁用密钥失败'
));
}
finally
{
setOperationLoading
(
prev
=>
({
...
prev
,
delete_disabled
:
false
}));
}
};
// Handle page change
const
handlePageChange
=
(
page
)
=>
{
setCurrentPage
(
page
);
loadKeyStatus
(
page
,
pageSize
);
};
// Handle page size change
const
handlePageSizeChange
=
(
size
)
=>
{
setPageSize
(
size
);
setCurrentPage
(
1
);
// Reset to first page
loadKeyStatus
(
1
,
size
);
};
// Handle status filter change
const
handleStatusFilterChange
=
(
status
)
=>
{
setStatusFilter
(
status
);
setCurrentPage
(
1
);
// Reset to first page when filter changes
loadKeyStatus
(
1
,
pageSize
,
status
);
};
// Effect to load data when modal opens
useEffect
(()
=>
{
if
(
visible
&&
channel
?.
id
)
{
setCurrentPage
(
1
);
// Reset to first page when opening
loadKeyStatus
(
1
,
pageSize
);
}
},
[
visible
,
channel
?.
id
]);
// Reset pagination when modal closes
useEffect
(()
=>
{
if
(
!
visible
)
{
setCurrentPage
(
1
);
setKeyStatusList
([]);
setTotal
(
0
);
setTotalPages
(
0
);
setEnabledCount
(
0
);
setManualDisabledCount
(
0
);
setAutoDisabledCount
(
0
);
setStatusFilter
(
null
);
// Reset filter
}
},
[
visible
]);
// Get status tag component
const
renderStatusTag
=
(
status
)
=>
{
switch
(
status
)
{
case
1
:
return
<
Tag
color=
'green'
shape=
'circle'
>
{
t
(
'已启用'
)
}
</
Tag
>;
case
2
:
return
<
Tag
color=
'red'
shape=
'circle'
>
{
t
(
'已禁用'
)
}
</
Tag
>;
case
3
:
return
<
Tag
color=
'orange'
shape=
'circle'
>
{
t
(
'自动禁用'
)
}
</
Tag
>;
default
:
return
<
Tag
color=
'grey'
shape=
'circle'
>
{
t
(
'未知状态'
)
}
</
Tag
>;
}
};
// Table columns definition
const
columns
=
[
{
title
:
t
(
'索引'
),
dataIndex
:
'index'
,
render
:
(
text
)
=>
`#
${
text
}
`
,
},
// {
// title: t('密钥预览'),
// dataIndex: 'key_preview',
// render: (text) => (
// <Text code style={{ fontSize: '12px' }}>
// {text}
// </Text>
// ),
// },
{
title
:
t
(
'状态'
),
dataIndex
:
'status'
,
width
:
100
,
render
:
(
status
)
=>
renderStatusTag
(
status
),
},
{
title
:
t
(
'禁用原因'
),
dataIndex
:
'reason'
,
width
:
220
,
render
:
(
reason
,
record
)
=>
{
if
(
record
.
status
===
1
||
!
reason
)
{
return
<
Text
type=
'quaternary'
>
-
</
Text
>;
}
return
(
<
Tooltip
content=
{
reason
}
>
<
Text
style=
{
{
maxWidth
:
'200px'
,
display
:
'block'
}
}
ellipsis
>
{
reason
}
</
Text
>
</
Tooltip
>
);
},
},
{
title
:
t
(
'禁用时间'
),
dataIndex
:
'disabled_time'
,
width
:
150
,
render
:
(
time
,
record
)
=>
{
if
(
record
.
status
===
1
||
!
time
)
{
return
<
Text
type=
'quaternary'
>
-
</
Text
>;
}
return
(
<
Tooltip
content=
{
timestamp2string
(
time
)
}
>
<
Text
style=
{
{
fontSize
:
'12px'
}
}
>
{
timestamp2string
(
time
)
}
</
Text
>
</
Tooltip
>
);
},
},
{
title
:
t
(
'操作'
),
key
:
'action'
,
width
:
120
,
render
:
(
_
,
record
)
=>
(
<
Space
>
{
record
.
status
===
1
?
(
<
Button
type=
'danger'
size=
'small'
loading=
{
operationLoading
[
`disable_${record.index}`
]
}
onClick=
{
()
=>
handleDisableKey
(
record
.
index
)
}
>
{
t
(
'禁用'
)
}
</
Button
>
)
:
(
<
Button
type=
'primary'
size=
'small'
loading=
{
operationLoading
[
`enable_${record.index}`
]
}
onClick=
{
()
=>
handleEnableKey
(
record
.
index
)
}
>
{
t
(
'启用'
)
}
</
Button
>
)
}
</
Space
>
),
},
];
return
(
<
Modal
title=
{
<
Space
>
<
IconSetting
/>
<
span
>
{
t
(
'多密钥管理'
)
}
-
{
channel
?.
name
}
</
span
>
</
Space
>
}
visible=
{
visible
}
onCancel=
{
onCancel
}
width=
{
800
}
height=
{
600
}
footer=
{
<
Space
>
<
Button
onClick=
{
onCancel
}
>
{
t
(
'关闭'
)
}
</
Button
>
<
Button
icon=
{
<
IconRefresh
/>
}
onClick=
{
()
=>
loadKeyStatus
(
currentPage
,
pageSize
)
}
loading=
{
loading
}
>
{
t
(
'刷新'
)
}
</
Button
>
<
Popconfirm
title=
{
t
(
'确定要启用所有密钥吗?'
)
}
onConfirm=
{
handleEnableAll
}
position=
{
'topRight'
}
>
<
Button
type=
'primary'
loading=
{
operationLoading
.
enable_all
}
>
{
t
(
'启用全部'
)
}
</
Button
>
</
Popconfirm
>
{
enabledCount
>
0
&&
(
<
Popconfirm
title=
{
t
(
'确定要禁用所有的密钥吗?'
)
}
onConfirm=
{
handleDisableAll
}
okType=
{
'danger'
}
position=
{
'topRight'
}
>
<
Button
type=
'danger'
loading=
{
operationLoading
.
disable_all
}
>
{
t
(
'禁用全部'
)
}
</
Button
>
</
Popconfirm
>
)
}
<
Popconfirm
title=
{
t
(
'确定要删除所有已自动禁用的密钥吗?'
)
}
content=
{
t
(
'此操作不可撤销,将永久删除已自动禁用的密钥'
)
}
onConfirm=
{
handleDeleteDisabledKeys
}
okType=
{
'danger'
}
position=
{
'topRight'
}
>
<
Button
type=
'danger'
icon=
{
<
IconDelete
/>
}
loading=
{
operationLoading
.
delete_disabled
}
>
{
t
(
'删除自动禁用密钥'
)
}
</
Button
>
</
Popconfirm
>
</
Space
>
}
>
<
div
style=
{
{
padding
:
'16px 0'
}
}
>
{
/* Statistics Banner */
}
<
Banner
type=
'info'
style=
{
{
marginBottom
:
'16px'
}
}
description=
{
<
div
>
<
Text
>
{
t
(
'总共
{{
total
}}
个密钥,
{{
enabled
}}
个已启用,
{{
manual
}}
个手动禁用,
{{
auto
}}
个自动禁用'
,
{
total
:
total
,
enabled
:
enabledCount
,
manual
:
manualDisabledCount
,
auto
:
autoDisabledCount
})
}
</
Text
>
{
channel
?.
channel_info
?.
multi_key_mode
&&
(
<
div
style=
{
{
marginTop
:
'4px'
}
}
>
<
Text
type=
'quaternary'
style=
{
{
fontSize
:
'12px'
}
}
>
{
t
(
'多密钥模式'
)
}
:
{
channel
.
channel_info
.
multi_key_mode
===
'random'
?
t
(
'随机'
)
:
t
(
'轮询'
)
}
</
Text
>
</
div
>
)
}
</
div
>
}
/>
{
/* Filter Controls */
}
<
div
style=
{
{
marginBottom
:
'16px'
,
display
:
'flex'
,
alignItems
:
'center'
,
gap
:
'12px'
}
}
>
<
Text
style=
{
{
fontSize
:
'14px'
,
fontWeight
:
'500'
}
}
>
{
t
(
'状态筛选'
)
}
:
</
Text
>
<
Select
value=
{
statusFilter
}
onChange=
{
handleStatusFilterChange
}
style=
{
{
width
:
'120px'
}
}
size=
'small'
placeholder=
{
t
(
'全部状态'
)
}
>
<
Select
.
Option
value=
{
null
}
>
{
t
(
'全部状态'
)
}
</
Select
.
Option
>
<
Select
.
Option
value=
{
1
}
>
{
t
(
'已启用'
)
}
</
Select
.
Option
>
<
Select
.
Option
value=
{
2
}
>
{
t
(
'手动禁用'
)
}
</
Select
.
Option
>
<
Select
.
Option
value=
{
3
}
>
{
t
(
'自动禁用'
)
}
</
Select
.
Option
>
</
Select
>
{
statusFilter
!==
null
&&
(
<
Text
type=
'quaternary'
style=
{
{
fontSize
:
'12px'
}
}
>
{
t
(
'当前显示
{{
count
}}
条筛选结果'
,
{
count
:
total
})
}
</
Text
>
)
}
</
div
>
{
/* Key Status Table */
}
<
Spin
spinning=
{
loading
}
>
{
keyStatusList
.
length
>
0
?
(
<>
<
Table
columns=
{
columns
}
dataSource=
{
keyStatusList
}
pagination=
{
false
}
size=
'small'
bordered
rowKey=
'index'
style=
{
{
marginBottom
:
'16px'
}
}
/>
{
/* Pagination */
}
{
total
>
0
&&
(
<
div
style=
{
{
display
:
'flex'
,
justifyContent
:
'space-between'
,
alignItems
:
'center'
}
}
>
<
Text
type=
'quaternary'
style=
{
{
fontSize
:
'12px'
}
}
>
{
t
(
'显示第
{{
start
}}
-
{{
end
}}
条,共
{{
total
}}
条'
,
{
start
:
(
currentPage
-
1
)
*
pageSize
+
1
,
end
:
Math
.
min
(
currentPage
*
pageSize
,
total
),
total
:
total
})
}
</
Text
>
<
div
style=
{
{
display
:
'flex'
,
alignItems
:
'center'
,
gap
:
'12px'
}
}
>
<
Text
type=
'quaternary'
style=
{
{
fontSize
:
'12px'
}
}
>
{
t
(
'每页显示'
)
}
:
</
Text
>
<
Select
value=
{
pageSize
}
onChange=
{
handlePageSizeChange
}
size=
'small'
style=
{
{
width
:
'80px'
}
}
>
<
Select
.
Option
value=
{
50
}
>
50
</
Select
.
Option
>
<
Select
.
Option
value=
{
100
}
>
100
</
Select
.
Option
>
<
Select
.
Option
value=
{
500
}
>
500
</
Select
.
Option
>
<
Select
.
Option
value=
{
1000
}
>
1000
</
Select
.
Option
>
</
Select
>
<
Pagination
current=
{
currentPage
}
total=
{
total
}
pageSize=
{
pageSize
}
showSizeChanger=
{
false
}
showQuickJumper
size=
'small'
onChange=
{
handlePageChange
}
showTotal=
{
(
total
,
range
)
=>
t
(
'第
{{
current
}}
/ {{total}} 页', {
current
:
currentPage
,
total
:
totalPages
})
}
/>
</
div
>
</
div
>
)
}
</>
)
:
(
!
loading
&&
(
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
title=
{
t
(
'暂无密钥数据'
)
}
description=
{
t
(
'请检查渠道配置或刷新重试'
)
}
/>
)
)
}
</
Spin
>
</
div
>
</
Modal
>
);
};
export
default
MultiKeyManageModal
;
\ No newline at end of file
web/src/hooks/channels/useChannelsData.js
View file @
711a7e34
...
...
@@ -83,6 +83,10 @@ export const useChannelsData = () => {
const
[
isProcessingQueue
,
setIsProcessingQueue
]
=
useState
(
false
);
const
[
modelTablePage
,
setModelTablePage
]
=
useState
(
1
);
// Multi-key management states
const
[
showMultiKeyManageModal
,
setShowMultiKeyManageModal
]
=
useState
(
false
);
const
[
currentMultiKeyChannel
,
setCurrentMultiKeyChannel
]
=
useState
(
null
);
// Refs
const
requestCounter
=
useRef
(
0
);
const
allSelectingRef
=
useRef
(
false
);
...
...
@@ -885,6 +889,12 @@ export const useChannelsData = () => {
setModelTablePage
,
allSelectingRef
,
// Multi-key management states
showMultiKeyManageModal
,
setShowMultiKeyManageModal
,
currentMultiKeyChannel
,
setCurrentMultiKeyChannel
,
// Form
formApi
,
setFormApi
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment