/* 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 . 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, Select, Row, Col, Badge, Progress, Card, } from '@douyinfe/semi-ui'; import { IllustrationNoResult, IllustrationNoResultDark, } from '@douyinfe/semi-illustrations'; import { API, showError, showSuccess, timestamp2string, } from '../../../../helpers'; const { Text } = 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(10); 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 || 10); 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 })); } }; // Delete a specific key const handleDeleteKey = async (keyIndex) => { const operationId = `delete_${keyIndex}`; setOperationLoading((prev) => ({ ...prev, [operationId]: true })); try { const res = await API.post('/api/channel/multi_key/manage', { channel_id: channel.id, action: 'delete_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 })); } }; // 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]); // Percentages for progress display const enabledPercent = total > 0 ? Math.round((enabledCount / total) * 100) : 0; const manualDisabledPercent = total > 0 ? Math.round((manualDisabledCount / total) * 100) : 0; const autoDisabledPercent = total > 0 ? Math.round((autoDisabledCount / total) * 100) : 0; // 取消饼图:不再需要图表数据与配置 // Get status tag component const renderStatusTag = (status) => { switch (status) { case 1: return ( {t('已启用')} ); case 2: return ( {t('已禁用')} ); case 3: return ( {t('自动禁用')} ); default: return ( {t('未知状态')} ); } }; // Table columns definition const columns = [ { title: t('索引'), dataIndex: 'index', render: (text) => `#${text}`, }, // { // title: t('密钥预览'), // dataIndex: 'key_preview', // render: (text) => ( // // {text} // // ), // }, { title: t('状态'), dataIndex: 'status', render: (status) => renderStatusTag(status), }, { title: t('禁用原因'), dataIndex: 'reason', render: (reason, record) => { if (record.status === 1 || !reason) { return -; } return ( {reason} ); }, }, { title: t('禁用时间'), dataIndex: 'disabled_time', render: (time, record) => { if (record.status === 1 || !time) { return -; } return ( {timestamp2string(time)} ); }, }, { title: t('操作'), key: 'action', fixed: 'right', width: 150, render: (_, record) => ( {record.status === 1 ? ( ) : ( )} handleDeleteKey(record.index)} okType={'danger'} position={'topRight'} > ), }, ]; return ( {t('多密钥管理')} {channel?.name && ( {channel.name} )} {t('总密钥数')}: {total} {channel?.channel_info?.multi_key_mode && ( {channel.channel_info.multi_key_mode === 'random' ? t('随机模式') : t('轮询模式')} )} } visible={visible} onCancel={onCancel} width={900} footer={null} >
{/* Stats & Mode */}
{t('已启用')}
{enabledCount} / {total}
{t('手动禁用')}
{manualDisabledCount} / {total}
{t('自动禁用')}
{autoDisabledCount} / {total}
{/* Table */}
( {manualDisabledCount + autoDisabledCount > 0 && ( )} {enabledCount > 0 && ( )} )} columns={columns} dataSource={keyStatusList} pagination={{ currentPage: currentPage, pageSize: pageSize, total: total, showSizeChanger: true, showQuickJumper: true, pageSizeOpts: [10, 20, 50, 100], onChange: (page, size) => { setCurrentPage(page); loadKeyStatus(page, size); }, onShowSizeChange: (current, size) => { setCurrentPage(1); handlePageSizeChange(size); }, }} size='small' bordered={false} rowKey='index' scroll={{ x: 'max-content' }} empty={ } darkModeImage={ } title={t('暂无密钥数据')} description={t('请检查渠道配置或刷新重试')} style={{ padding: 30 }} /> } /> ); }; export default MultiKeyManageModal;