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
808c8c5b
authored
Aug 04, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add status filtering and bulk enable/disable functionality in multi-key management
parent
8fe43be3
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
287 additions
and
89 deletions
+287
-89
controller/channel.go
+134
-45
web/src/components/table/channels/modals/MultiKeyManageModal.jsx
+153
-44
No files found.
controller/channel.go
View file @
808c8c5b
...
...
@@ -1057,6 +1057,7 @@ type MultiKeyManageRequest struct {
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
...
...
@@ -1109,7 +1110,6 @@ func ManageMultiKeys(c *gin.Context) {
switch
request
.
Action
{
case
"get_key_status"
:
keys
:=
channel
.
GetKeys
()
total
:=
len
(
keys
)
// Default pagination parameters
page
:=
request
.
Page
...
...
@@ -1121,23 +1121,11 @@ func ManageMultiKeys(c *gin.Context) {
pageSize
=
50
// Default page size
}
// Calculate pagination
totalPages
:=
(
total
+
pageSize
-
1
)
/
pageSize
if
page
>
totalPages
&&
totalPages
>
0
{
page
=
totalPages
}
// Calculate range
start
:=
(
page
-
1
)
*
pageSize
end
:=
start
+
pageSize
if
end
>
total
{
end
=
total
}
// Statistics for all keys
// Statistics for all keys (unchanged by filtering)
var
enabledCount
,
manualDisabledCount
,
autoDisabledCount
int
var
keyStatusList
[]
KeyStatus
// Build all key status data first
var
allKeyStatusList
[]
KeyStatus
for
i
,
key
:=
range
keys
{
status
:=
1
// default enabled
var
disabledTime
int64
...
...
@@ -1149,7 +1137,7 @@ func ManageMultiKeys(c *gin.Context) {
}
}
// Count for statistics
// Count for statistics
(all keys)
switch
status
{
case
1
:
enabledCount
++
...
...
@@ -1159,45 +1147,77 @@ func ManageMultiKeys(c *gin.Context) {
autoDisabledCount
++
}
// Only include keys in current page
if
i
>=
start
&&
i
<
end
{
if
status
!=
1
{
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
!=
nil
{
disabledTime
=
channel
.
ChannelInfo
.
MultiKeyDisabledTime
[
i
]
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
!=
nil
{
reason
=
channel
.
ChannelInfo
.
MultiKeyDisabledReason
[
i
]
}
if
status
!=
1
{
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
!=
nil
{
disabledTime
=
channel
.
ChannelInfo
.
MultiKeyDisabledTime
[
i
]
}
// Create key preview (first 10 chars)
keyPreview
:=
key
if
len
(
key
)
>
10
{
keyPreview
=
key
[
:
10
]
+
"..."
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
!=
nil
{
reason
=
channel
.
ChannelInfo
.
MultiKeyDisabledReason
[
i
]
}
}
keyStatusList
=
append
(
keyStatusList
,
KeyStatus
{
Index
:
i
,
Status
:
status
,
DisabledTime
:
disabledTime
,
Reason
:
reason
,
KeyPreview
:
keyPreview
,
})
// 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
:
k
eyStatusList
,
Total
:
total
,
Keys
:
pageK
eyStatusList
,
Total
:
filteredTotal
,
// Total of filtered results
Page
:
page
,
PageSize
:
pageSize
,
TotalPages
:
totalPages
,
EnabledCount
:
enabledCount
,
ManualDisabledCount
:
manualDisabledCount
,
AutoDisabledCount
:
autoDisabledCount
,
EnabledCount
:
enabledCount
,
// Overall statistics
ManualDisabledCount
:
manualDisabledCount
,
// Overall statistics
AutoDisabledCount
:
autoDisabledCount
,
// Overall statistics
},
})
return
...
...
@@ -1231,8 +1251,6 @@ func ManageMultiKeys(c *gin.Context) {
}
channel
.
ChannelInfo
.
MultiKeyStatusList
[
keyIndex
]
=
2
// disabled
channel
.
ChannelInfo
.
MultiKeyDisabledTime
[
keyIndex
]
=
common
.
GetTimestamp
()
channel
.
ChannelInfo
.
MultiKeyDisabledReason
[
keyIndex
]
=
"手动禁用"
err
=
channel
.
Update
()
if
err
!=
nil
{
...
...
@@ -1289,6 +1307,77 @@ func ManageMultiKeys(c *gin.Context) {
})
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
...
...
web/src/components/table/channels/modals/MultiKeyManageModal.jsx
View file @
808c8c5b
...
...
@@ -67,18 +67,28 @@ const MultiKeyManageModal = ({
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
)
=>
{
const
loadKeyStatus
=
async
(
page
=
currentPage
,
size
=
pageSize
,
status
=
statusFilter
)
=>
{
if
(
!
channel
?.
id
)
return
;
setLoading
(
true
);
try
{
const
re
s
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
{
const
re
questData
=
{
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
;
...
...
@@ -88,7 +98,7 @@ const MultiKeyManageModal = ({
setPageSize
(
data
.
page_size
||
50
);
setTotalPages
(
data
.
total_pages
||
0
);
// Update statistics
// 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
);
...
...
@@ -155,6 +165,58 @@ const MultiKeyManageModal = ({
}
};
// 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
}));
...
...
@@ -194,6 +256,13 @@ const MultiKeyManageModal = ({
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
)
{
...
...
@@ -212,6 +281,7 @@ const MultiKeyManageModal = ({
setEnabledCount
(
0
);
setManualDisabledCount
(
0
);
setAutoDisabledCount
(
0
);
setStatusFilter
(
null
);
// Reset filter
}
},
[
visible
]);
...
...
@@ -236,15 +306,15 @@ const MultiKeyManageModal = ({
dataIndex
:
'index'
,
render
:
(
text
)
=>
`#
${
text
}
`
,
},
{
title
:
t
(
'密钥预览'
),
dataIndex
:
'key_preview'
,
render
:
(
text
)
=>
(
<
Text
code
style=
{
{
fontSize
:
'12px'
}
}
>
{
text
}
</
Text
>
),
},
//
{
//
title: t('密钥预览'),
//
dataIndex: 'key_preview',
//
render: (text) => (
//
<Text code style={{ fontSize: '12px' }}>
//
{text}
//
</Text>
//
),
//
},
{
title
:
t
(
'状态'
),
dataIndex
:
'status'
,
...
...
@@ -292,33 +362,23 @@ const MultiKeyManageModal = ({
render
:
(
_
,
record
)
=>
(
<
Space
>
{
record
.
status
===
1
?
(
<
Popconfirm
title=
{
t
(
'确定要禁用此密钥吗?'
)
}
content=
{
t
(
'禁用后该密钥将不再被使用'
)
}
onConfirm=
{
()
=>
handleDisableKey
(
record
.
index
)
}
<
Button
type=
'danger'
size=
'small'
loading=
{
operationLoading
[
`disable_${record.index}`
]
}
onClick=
{
()
=>
handleDisableKey
(
record
.
index
)
}
>
<
Button
type=
'danger'
size=
'small'
loading=
{
operationLoading
[
`disable_${record.index}`
]
}
>
{
t
(
'禁用'
)
}
</
Button
>
</
Popconfirm
>
{
t
(
'禁用'
)
}
</
Button
>
)
:
(
<
Popconfirm
title=
{
t
(
'确定要启用此密钥吗?'
)
}
content=
{
t
(
'启用后该密钥将重新被使用'
)
}
onConfirm=
{
()
=>
handleEnableKey
(
record
.
index
)
}
<
Button
type=
'primary'
size=
'small'
loading=
{
operationLoading
[
`enable_${record.index}`
]
}
onClick=
{
()
=>
handleEnableKey
(
record
.
index
)
}
>
<
Button
type=
'primary'
size=
'small'
loading=
{
operationLoading
[
`enable_${record.index}`
]
}
>
{
t
(
'启用'
)
}
</
Button
>
</
Popconfirm
>
{
t
(
'启用'
)
}
</
Button
>
)
}
</
Space
>
),
...
...
@@ -347,21 +407,48 @@ const MultiKeyManageModal = ({
>
{
t
(
'刷新'
)
}
</
Button
>
{
autoDisabledCount
>
0
&&
(
<
Popconfirm
title=
{
t
(
'确定要启用所有密钥吗?'
)
}
onConfirm=
{
handleEnableAll
}
position=
{
'topRight'
}
>
<
Button
type=
'primary'
loading=
{
operationLoading
.
enable_all
}
>
{
t
(
'启用全部'
)
}
</
Button
>
</
Popconfirm
>
{
enabledCount
>
0
&&
(
<
Popconfirm
title=
{
t
(
'确定要删除所有已自动禁用的密钥吗?'
)
}
content=
{
t
(
'此操作不可撤销,将永久删除已自动禁用的密钥'
)
}
onConfirm=
{
handleDeleteDisabledKeys
}
title=
{
t
(
'确定要禁用所有的密钥吗?'
)
}
onConfirm=
{
handleDisableAll
}
okType=
{
'danger'
}
position=
{
'topRight'
}
>
<
Button
type=
'danger'
icon=
{
<
IconDelete
/>
}
loading=
{
operationLoading
.
delete_disabled
}
loading=
{
operationLoading
.
disable_all
}
>
{
t
(
'
删除自动禁用密钥
'
)
}
{
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
>
}
>
...
...
@@ -391,6 +478,28 @@ const MultiKeyManageModal = ({
}
/>
{
/* 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
?
(
...
...
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