feat: enhance multi-key management with pagination and statistics
This commit is contained in:
@@ -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": "",
|
||||
@@ -1036,11 +1055,21 @@ 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
|
||||
}
|
||||
|
||||
// MultiKeyStatusResponse represents the response for key status query
|
||||
type MultiKeyStatusResponse struct {
|
||||
Keys []KeyStatus `json:"keys"`
|
||||
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 {
|
||||
@@ -1080,8 +1109,35 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
switch request.Action {
|
||||
case "get_key_status":
|
||||
keys := channel.GetKeys()
|
||||
var keyStatusList []KeyStatus
|
||||
total := len(keys)
|
||||
|
||||
// Default pagination parameters
|
||||
page := request.Page
|
||||
pageSize := request.PageSize
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
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
|
||||
var enabledCount, manualDisabledCount, autoDisabledCount int
|
||||
|
||||
var keyStatusList []KeyStatus
|
||||
for i, key := range keys {
|
||||
status := 1 // default enabled
|
||||
var disabledTime int64
|
||||
@@ -1093,34 +1149,56 @@ func ManageMultiKeys(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if status != 1 {
|
||||
if channel.ChannelInfo.MultiKeyDisabledTime != nil {
|
||||
disabledTime = channel.ChannelInfo.MultiKeyDisabledTime[i]
|
||||
}
|
||||
if channel.ChannelInfo.MultiKeyDisabledReason != nil {
|
||||
reason = channel.ChannelInfo.MultiKeyDisabledReason[i]
|
||||
}
|
||||
// Count for statistics
|
||||
switch status {
|
||||
case 1:
|
||||
enabledCount++
|
||||
case 2:
|
||||
manualDisabledCount++
|
||||
case 3:
|
||||
autoDisabledCount++
|
||||
}
|
||||
|
||||
// Create key preview (first 10 chars)
|
||||
keyPreview := key
|
||||
if len(key) > 10 {
|
||||
keyPreview = key[:10] + "..."
|
||||
}
|
||||
// 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]
|
||||
}
|
||||
}
|
||||
|
||||
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] + "..."
|
||||
}
|
||||
|
||||
keyStatusList = append(keyStatusList, KeyStatus{
|
||||
Index: i,
|
||||
Status: status,
|
||||
DisabledTime: disabledTime,
|
||||
Reason: reason,
|
||||
KeyPreview: keyPreview,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": MultiKeyStatusResponse{Keys: keyStatusList},
|
||||
"data": MultiKeyStatusResponse{
|
||||
Keys: keyStatusList,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
EnabledCount: enabledCount,
|
||||
ManualDisabledCount: manualDisabledCount,
|
||||
AutoDisabledCount: autoDisabledCount,
|
||||
},
|
||||
})
|
||||
return
|
||||
|
||||
|
||||
@@ -53,12 +53,12 @@ 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
|
||||
MultiKeyDisabledReason map[int]string `json:"multi_key_disabled_reason"` // key禁用原因列表,key index -> reason
|
||||
MultiKeyDisabledTime map[int]int64 `json:"multi_key_disabled_time"` // key禁用时间列表,key index -> time
|
||||
MultiKeyPollingIndex int `json:"multi_key_polling_index"` // 多Key模式下轮询的key索引
|
||||
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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
Popconfirm,
|
||||
Empty,
|
||||
Spin,
|
||||
Banner
|
||||
Banner,
|
||||
Select,
|
||||
Pagination
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconRefresh,
|
||||
@@ -53,24 +55,48 @@ const MultiKeyManageModal = ({
|
||||
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);
|
||||
|
||||
// Load key status data
|
||||
const loadKeyStatus = async () => {
|
||||
const loadKeyStatus = async (page = currentPage, size = pageSize) => {
|
||||
if (!channel?.id) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await API.post('/api/channel/multi_key/manage', {
|
||||
channel_id: channel.id,
|
||||
action: 'get_key_status'
|
||||
action: 'get_key_status',
|
||||
page: page,
|
||||
page_size: size
|
||||
});
|
||||
|
||||
if (res.data.success) {
|
||||
setKeyStatusList(res.data.data.keys || []);
|
||||
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
|
||||
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);
|
||||
@@ -91,7 +117,7 @@ const MultiKeyManageModal = ({
|
||||
|
||||
if (res.data.success) {
|
||||
showSuccess(t('密钥已禁用'));
|
||||
await loadKeyStatus(); // Reload data
|
||||
await loadKeyStatus(currentPage, pageSize); // Reload current page
|
||||
onRefresh && onRefresh(); // Refresh parent component
|
||||
} else {
|
||||
showError(res.data.message);
|
||||
@@ -117,7 +143,7 @@ const MultiKeyManageModal = ({
|
||||
|
||||
if (res.data.success) {
|
||||
showSuccess(t('密钥已启用'));
|
||||
await loadKeyStatus(); // Reload data
|
||||
await loadKeyStatus(currentPage, pageSize); // Reload current page
|
||||
onRefresh && onRefresh(); // Refresh parent component
|
||||
} else {
|
||||
showError(res.data.message);
|
||||
@@ -141,7 +167,9 @@ const MultiKeyManageModal = ({
|
||||
|
||||
if (res.data.success) {
|
||||
showSuccess(res.data.message);
|
||||
await loadKeyStatus(); // Reload data
|
||||
// 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);
|
||||
@@ -153,13 +181,40 @@ const MultiKeyManageModal = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
// Effect to load data when modal opens
|
||||
useEffect(() => {
|
||||
if (visible && channel?.id) {
|
||||
loadKeyStatus();
|
||||
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);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
// Get status tag component
|
||||
const renderStatusTag = (status) => {
|
||||
switch (status) {
|
||||
@@ -270,12 +325,6 @@ const MultiKeyManageModal = ({
|
||||
},
|
||||
];
|
||||
|
||||
// Calculate statistics
|
||||
const enabledCount = keyStatusList.filter(key => key.status === 1).length;
|
||||
const manualDisabledCount = keyStatusList.filter(key => key.status === 2).length;
|
||||
const autoDisabledCount = keyStatusList.filter(key => key.status === 3).length;
|
||||
const totalCount = keyStatusList.length;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
@@ -293,7 +342,7 @@ const MultiKeyManageModal = ({
|
||||
<Button onClick={onCancel}>{t('关闭')}</Button>
|
||||
<Button
|
||||
icon={<IconRefresh />}
|
||||
onClick={loadKeyStatus}
|
||||
onClick={() => loadKeyStatus(currentPage, pageSize)}
|
||||
loading={loading}
|
||||
>
|
||||
{t('刷新')}
|
||||
@@ -325,7 +374,7 @@ const MultiKeyManageModal = ({
|
||||
<div>
|
||||
<Text>
|
||||
{t('总共 {{total}} 个密钥,{{enabled}} 个已启用,{{manual}} 个手动禁用,{{auto}} 个自动禁用', {
|
||||
total: totalCount,
|
||||
total: total,
|
||||
enabled: enabledCount,
|
||||
manual: manualDisabledCount,
|
||||
auto: autoDisabledCount
|
||||
@@ -345,15 +394,63 @@ const MultiKeyManageModal = ({
|
||||
{/* Key Status Table */}
|
||||
<Spin spinning={loading}>
|
||||
{keyStatusList.length > 0 ? (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={keyStatusList}
|
||||
pagination={false}
|
||||
size='small'
|
||||
bordered
|
||||
rowKey='index'
|
||||
style={{ maxHeight: '400px', overflow: 'auto' }}
|
||||
/>
|
||||
<>
|
||||
<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={200}>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
|
||||
|
||||
Reference in New Issue
Block a user