Selaa lähdekoodia

feat: add status filtering and bulk enable/disable functionality in multi-key management

CaIon 7 kuukautta sitten
vanhempi
commit
12b4e80d4b
2 muutettua tiedostoa jossa 287 lisäystä ja 89 poistoa
  1. 134 45
      controller/channel.go
  2. 153 44
      web/src/components/table/channels/modals/MultiKeyManageModal.jsx

+ 134 - 45
controller/channel.go

@@ -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:                keyStatusList,
-				Total:               total,
+				Keys:                pageKeyStatusList,
+				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

+ 153 - 44
web/src/components/table/channels/modals/MultiKeyManageModal.jsx

@@ -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 res = await API.post('/api/channel/multi_key/manage', {
+      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;
@@ -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 ? (