🎨 refactor: MultiKeyManageModal: cleaner stats UI, remove chart, integrate toolbar/pagination, and improve UX

- Replace custom dots with Semi Badge types (success/danger/warning); add compact Progress bars
- Remove pie chart and related deps/config; move total key count and mode tags into the modal title
- Rework header using Row/Col; three equal stat cards (enabled/manual-disabled/auto-disabled)
- Integrate toolbar into Table title; wrap content with Card; use Table’s native empty state
- Make “Enable All” conditional (hidden when all keys are enabled), mirroring “Disable All”
- Unify numeric typography (current/total same size) for better readability
- Default page size set to 10; fallback to 10 when backend page_size is absent; page-size options: 10/20/50/100
- Cleanup imports and dead code (remove VChart and pie-spec logic)
- Minor spacing polish (extra bottom margin before table), no footer buttons
This commit is contained in:
t0ng7u
2025-08-10 00:55:18 +08:00
parent 71ba3fa310
commit ada434fb20
3 changed files with 235 additions and 218 deletions

View File

@@ -544,7 +544,7 @@ export const getChannelsColumns = ({
menu={[ menu={[
{ {
node: 'item', node: 'item',
name: t('多key管理'), name: t('多密钥管理'),
onClick: () => { onClick: () => {
setCurrentMultiKeyChannel(record); setCurrentMultiKeyChannel(record);
setShowMultiKeyManageModal(true); setShowMultiKeyManageModal(true);

View File

@@ -30,20 +30,17 @@ import {
Popconfirm, Popconfirm,
Empty, Empty,
Spin, Spin,
Banner,
Select, Select,
Pagination Row,
Col,
Badge,
Progress,
Card
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
IconRefresh,
IconDelete,
IconClose,
IconSave,
IconSetting
} from '@douyinfe/semi-icons';
import { API, showError, showSuccess, timestamp2string } from '../../../../helpers/index.js'; import { API, showError, showSuccess, timestamp2string } from '../../../../helpers/index.js';
const { Text, Title } = Typography; const { Text } = Typography;
const MultiKeyManageModal = ({ const MultiKeyManageModal = ({
visible, visible,
@@ -55,13 +52,13 @@ const MultiKeyManageModal = ({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [keyStatusList, setKeyStatusList] = useState([]); const [keyStatusList, setKeyStatusList] = useState([]);
const [operationLoading, setOperationLoading] = useState({}); const [operationLoading, setOperationLoading] = useState({});
// Pagination states // Pagination states
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(50); const [pageSize, setPageSize] = useState(10);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [totalPages, setTotalPages] = useState(0); const [totalPages, setTotalPages] = useState(0);
// Statistics states // Statistics states
const [enabledCount, setEnabledCount] = useState(0); const [enabledCount, setEnabledCount] = useState(0);
const [manualDisabledCount, setManualDisabledCount] = useState(0); const [manualDisabledCount, setManualDisabledCount] = useState(0);
@@ -73,7 +70,7 @@ const MultiKeyManageModal = ({
// Load key status data // Load key status data
const loadKeyStatus = async (page = currentPage, size = pageSize, status = statusFilter) => { const loadKeyStatus = async (page = currentPage, size = pageSize, status = statusFilter) => {
if (!channel?.id) return; if (!channel?.id) return;
setLoading(true); setLoading(true);
try { try {
const requestData = { const requestData = {
@@ -82,22 +79,22 @@ const MultiKeyManageModal = ({
page: page, page: page,
page_size: size page_size: size
}; };
// Add status filter if specified // Add status filter if specified
if (status !== null) { if (status !== null) {
requestData.status = status; requestData.status = status;
} }
const res = await API.post('/api/channel/multi_key/manage', requestData); const res = await API.post('/api/channel/multi_key/manage', requestData);
if (res.data.success) { if (res.data.success) {
const data = res.data.data; const data = res.data.data;
setKeyStatusList(data.keys || []); setKeyStatusList(data.keys || []);
setTotal(data.total || 0); setTotal(data.total || 0);
setCurrentPage(data.page || 1); setCurrentPage(data.page || 1);
setPageSize(data.page_size || 50); setPageSize(data.page_size || 10);
setTotalPages(data.total_pages || 0); setTotalPages(data.total_pages || 0);
// Update statistics (these are always the overall statistics) // Update statistics (these are always the overall statistics)
setEnabledCount(data.enabled_count || 0); setEnabledCount(data.enabled_count || 0);
setManualDisabledCount(data.manual_disabled_count || 0); setManualDisabledCount(data.manual_disabled_count || 0);
@@ -117,14 +114,14 @@ const MultiKeyManageModal = ({
const handleDisableKey = async (keyIndex) => { const handleDisableKey = async (keyIndex) => {
const operationId = `disable_${keyIndex}`; const operationId = `disable_${keyIndex}`;
setOperationLoading(prev => ({ ...prev, [operationId]: true })); setOperationLoading(prev => ({ ...prev, [operationId]: true }));
try { try {
const res = await API.post('/api/channel/multi_key/manage', { const res = await API.post('/api/channel/multi_key/manage', {
channel_id: channel.id, channel_id: channel.id,
action: 'disable_key', action: 'disable_key',
key_index: keyIndex key_index: keyIndex
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(t('密钥已禁用')); showSuccess(t('密钥已禁用'));
await loadKeyStatus(currentPage, pageSize); // Reload current page await loadKeyStatus(currentPage, pageSize); // Reload current page
@@ -143,14 +140,14 @@ const MultiKeyManageModal = ({
const handleEnableKey = async (keyIndex) => { const handleEnableKey = async (keyIndex) => {
const operationId = `enable_${keyIndex}`; const operationId = `enable_${keyIndex}`;
setOperationLoading(prev => ({ ...prev, [operationId]: true })); setOperationLoading(prev => ({ ...prev, [operationId]: true }));
try { try {
const res = await API.post('/api/channel/multi_key/manage', { const res = await API.post('/api/channel/multi_key/manage', {
channel_id: channel.id, channel_id: channel.id,
action: 'enable_key', action: 'enable_key',
key_index: keyIndex key_index: keyIndex
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(t('密钥已启用')); showSuccess(t('密钥已启用'));
await loadKeyStatus(currentPage, pageSize); // Reload current page await loadKeyStatus(currentPage, pageSize); // Reload current page
@@ -168,13 +165,13 @@ const MultiKeyManageModal = ({
// Enable all disabled keys // Enable all disabled keys
const handleEnableAll = async () => { const handleEnableAll = async () => {
setOperationLoading(prev => ({ ...prev, enable_all: true })); setOperationLoading(prev => ({ ...prev, enable_all: true }));
try { try {
const res = await API.post('/api/channel/multi_key/manage', { const res = await API.post('/api/channel/multi_key/manage', {
channel_id: channel.id, channel_id: channel.id,
action: 'enable_all_keys' action: 'enable_all_keys'
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(res.data.message || t('已启用所有密钥')); showSuccess(res.data.message || t('已启用所有密钥'));
// Reset to first page after bulk operation // Reset to first page after bulk operation
@@ -194,13 +191,13 @@ const MultiKeyManageModal = ({
// Disable all enabled keys // Disable all enabled keys
const handleDisableAll = async () => { const handleDisableAll = async () => {
setOperationLoading(prev => ({ ...prev, disable_all: true })); setOperationLoading(prev => ({ ...prev, disable_all: true }));
try { try {
const res = await API.post('/api/channel/multi_key/manage', { const res = await API.post('/api/channel/multi_key/manage', {
channel_id: channel.id, channel_id: channel.id,
action: 'disable_all_keys' action: 'disable_all_keys'
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(res.data.message || t('已禁用所有密钥')); showSuccess(res.data.message || t('已禁用所有密钥'));
// Reset to first page after bulk operation // Reset to first page after bulk operation
@@ -220,13 +217,13 @@ const MultiKeyManageModal = ({
// Delete all disabled keys // Delete all disabled keys
const handleDeleteDisabledKeys = async () => { const handleDeleteDisabledKeys = async () => {
setOperationLoading(prev => ({ ...prev, delete_disabled: true })); setOperationLoading(prev => ({ ...prev, delete_disabled: true }));
try { try {
const res = await API.post('/api/channel/multi_key/manage', { const res = await API.post('/api/channel/multi_key/manage', {
channel_id: channel.id, channel_id: channel.id,
action: 'delete_disabled_keys' action: 'delete_disabled_keys'
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(res.data.message); showSuccess(res.data.message);
// Reset to first page after deletion as data structure might change // Reset to first page after deletion as data structure might change
@@ -285,17 +282,24 @@ const MultiKeyManageModal = ({
} }
}, [visible]); }, [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 // Get status tag component
const renderStatusTag = (status) => { const renderStatusTag = (status) => {
switch (status) { switch (status) {
case 1: case 1:
return <Tag color='green' shape='circle'>{t('已启用')}</Tag>; return <Tag color='green' shape='circle' size='small'>{t('已启用')}</Tag>;
case 2: case 2:
return <Tag color='red' shape='circle'>{t('已禁用')}</Tag>; return <Tag color='red' shape='circle' size='small'>{t('已禁用')}</Tag>;
case 3: case 3:
return <Tag color='orange' shape='circle'>{t('自动禁用')}</Tag>; return <Tag color='orange' shape='circle' size='small'>{t('自动禁用')}</Tag>;
default: default:
return <Tag color='grey' shape='circle'>{t('未知状态')}</Tag>; return <Tag color='grey' shape='circle' size='small'>{t('未知状态')}</Tag>;
} }
}; };
@@ -318,13 +322,11 @@ const MultiKeyManageModal = ({
{ {
title: t('状态'), title: t('状态'),
dataIndex: 'status', dataIndex: 'status',
width: 100,
render: (status) => renderStatusTag(status), render: (status) => renderStatusTag(status),
}, },
{ {
title: t('禁用原因'), title: t('禁用原因'),
dataIndex: 'reason', dataIndex: 'reason',
width: 220,
render: (reason, record) => { render: (reason, record) => {
if (record.status === 1 || !reason) { if (record.status === 1 || !reason) {
return <Text type='quaternary'>-</Text>; return <Text type='quaternary'>-</Text>;
@@ -341,7 +343,6 @@ const MultiKeyManageModal = ({
{ {
title: t('禁用时间'), title: t('禁用时间'),
dataIndex: 'disabled_time', dataIndex: 'disabled_time',
width: 150,
render: (time, record) => { render: (time, record) => {
if (record.status === 1 || !time) { if (record.status === 1 || !time) {
return <Text type='quaternary'>-</Text>; return <Text type='quaternary'>-</Text>;
@@ -358,7 +359,8 @@ const MultiKeyManageModal = ({
{ {
title: t('操作'), title: t('操作'),
key: 'action', key: 'action',
width: 120, fixed: 'right',
width: 100,
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
{record.status === 1 ? ( {record.status === 1 ? (
@@ -389,196 +391,194 @@ const MultiKeyManageModal = ({
<Modal <Modal
title={ title={
<Space> <Space>
<IconSetting /> <Text>{t('多密钥管理')}</Text>
<span>{t('多密钥管理')} - {channel?.name}</span> {channel?.name && (
<Tag size='small' shape='circle' color='white'>{channel.name}</Tag>
)}
<Tag size='small' shape='circle' color='white'>
{t('总密钥数')}: {total}
</Tag>
{channel?.channel_info?.multi_key_mode && (
<Tag size='small' shape='circle' color='white'>
{channel.channel_info.multi_key_mode === 'random' ? t('随机模式') : t('轮询模式')}
</Tag>
)}
</Space> </Space>
} }
visible={visible} visible={visible}
onCancel={onCancel} onCancel={onCancel}
width={900} width={900}
footer={ footer={null}
<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={{ height: '100%', display: 'flex', flexDirection: 'column' }}> <div className="flex flex-col mb-5">
{/* Statistics Banner */} {/* Stats & Mode */}
<Banner <div
type='info' className="rounded-xl p-4 mb-3"
style={{ marginBottom: '16px', flexShrink: 0 }} style={{
description={ background: 'var(--semi-color-bg-1)',
<div> border: '1px solid var(--semi-color-border)'
<Text> }}
{t('总共 {{total}} 个密钥,{{enabled}} 个已启用,{{manual}} 个手动禁用,{{auto}} 个自动禁用', { >
total: total, <Row gutter={16} align="middle">
enabled: enabledCount, <Col span={8}>
manual: manualDisabledCount, <div style={{ background: 'var(--semi-color-bg-0)', border: '1px solid var(--semi-color-border)', borderRadius: 12, padding: 12 }}>
auto: autoDisabledCount <div className="flex items-center gap-2 mb-2">
})} <Badge dot type='success' />
</Text> <Text type='tertiary'>{t('已启用')}</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>
)} <div className="flex items-end gap-2 mb-2">
</div> <Text style={{ fontSize: 18, fontWeight: 700, color: '#22c55e' }}>{enabledCount}</Text>
} <Text style={{ fontSize: 18, color: 'var(--semi-color-text-2)' }}>/ {total}</Text>
/> </div>
<Progress percent={enabledPercent} showInfo={false} size="small" stroke="#22c55e" style={{ height: 6, borderRadius: 999 }} />
{/* Filter Controls */} </div>
<div style={{ marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '12px', flexShrink: 0 }}> </Col>
<Text style={{ fontSize: '14px', fontWeight: '500' }}>{t('状态筛选')}:</Text> <Col span={8}>
<Select <div style={{ background: 'var(--semi-color-bg-0)', border: '1px solid var(--semi-color-border)', borderRadius: 12, padding: 12 }}>
value={statusFilter} <div className="flex items-center gap-2 mb-2">
onChange={handleStatusFilterChange} <Badge dot type='danger' />
style={{ width: '120px' }} <Text type='tertiary'>{t('手动禁用')}</Text>
size='small' </div>
placeholder={t('全部状态')} <div className="flex items-end gap-2 mb-2">
> <Text style={{ fontSize: 18, fontWeight: 700, color: '#ef4444' }}>{manualDisabledCount}</Text>
<Select.Option value={null}>{t('全部状态')}</Select.Option> <Text style={{ fontSize: 18, color: 'var(--semi-color-text-2)' }}>/ {total}</Text>
<Select.Option value={1}>{t('已启用')}</Select.Option> </div>
<Select.Option value={2}>{t('手动禁用')}</Select.Option> <Progress percent={manualDisabledPercent} showInfo={false} size="small" stroke="#ef4444" style={{ height: 6, borderRadius: 999 }} />
<Select.Option value={3}>{t('自动禁用')}</Select.Option> </div>
</Select> </Col>
{statusFilter !== null && ( <Col span={8}>
<Text type='quaternary' style={{ fontSize: '12px' }}> <div style={{ background: 'var(--semi-color-bg-0)', border: '1px solid var(--semi-color-border)', borderRadius: 12, padding: 12 }}>
{t('当前显示 {{count}} 条筛选结果', { count: total })} <div className="flex items-center gap-2 mb-2">
</Text> <Badge dot type='warning' />
)} <Text type='tertiary'>{t('自动禁用')}</Text>
</div>
<div className="flex items-end gap-2 mb-2">
<Text style={{ fontSize: 18, fontWeight: 700, color: '#f59e0b' }}>{autoDisabledCount}</Text>
<Text style={{ fontSize: 18, color: 'var(--semi-color-text-2)' }}>/ {total}</Text>
</div>
<Progress percent={autoDisabledPercent} showInfo={false} size="small" stroke="#f59e0b" style={{ height: 6, borderRadius: 999 }} />
</div>
</Col>
</Row>
</div> </div>
{/* Key Status Table */} {/* Table */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}> <div className="flex-1 flex flex-col min-h-0">
<Spin spinning={loading}> <Spin spinning={loading}>
{keyStatusList.length > 0 ? ( <Card className='!rounded-xl'>
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}> <Table
<div style={{ flex: 1, overflow: 'auto', marginBottom: '16px' }}> title={() => (
<Table <Row gutter={12} style={{ width: '100%' }}>
columns={columns} <Col span={14}>
dataSource={keyStatusList} <Row gutter={12} style={{ alignItems: 'center' }}>
pagination={false} <Col>
size='small' <Select
bordered value={statusFilter}
rowKey='index' onChange={handleStatusFilterChange}
scroll={{ y: 'calc(100vh - 400px)' }} size='small'
/> placeholder={t('全部状态')}
</div> >
<Select.Option value={null}>{t('全部状态')}</Select.Option>
{/* Pagination */} <Select.Option value={1}>{t('已启用')}</Select.Option>
{total > 0 && ( <Select.Option value={2}>{t('手动禁用')}</Select.Option>
<div style={{ <Select.Option value={3}>{t('自动禁用')}</Select.Option>
display: 'flex', </Select>
justifyContent: 'space-between', </Col>
alignItems: 'center', </Row>
flexShrink: 0, </Col>
padding: '12px 0', <Col span={10} style={{ display: 'flex', justifyContent: 'flex-end' }}>
borderTop: '1px solid var(--semi-color-border)', <Space>
backgroundColor: 'var(--semi-color-bg-1)' <Button
}}> size='small'
<Text type='quaternary' style={{ fontSize: '12px' }}> type='tertiary'
{t('显示第 {{start}}-{{end}} 条,共 {{total}} 条', { onClick={() => loadKeyStatus(currentPage, pageSize)}
start: (currentPage - 1) * pageSize + 1, loading={loading}
end: Math.min(currentPage * pageSize, total), >
total: total {t('刷新')}
})} </Button>
</Text> {(manualDisabledCount + autoDisabledCount) > 0 && (
<Popconfirm
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> title={t('确定要启用所有密钥吗?')}
<Text type='quaternary' style={{ fontSize: '12px' }}> onConfirm={handleEnableAll}
{t('每页显示')}: position={'topRight'}
</Text> >
<Select <Button
value={pageSize} size='small'
onChange={handlePageSizeChange} type='primary'
size='small' loading={operationLoading.enable_all}
style={{ width: '80px' }} >
> {t('启用全部')}
<Select.Option value={50}>50</Select.Option> </Button>
<Select.Option value={100}>100</Select.Option> </Popconfirm>
<Select.Option value={500}>500</Select.Option> )}
<Select.Option value={1000}>1000</Select.Option> {enabledCount > 0 && (
</Select> <Popconfirm
title={t('确定要禁用所有的密钥吗?')}
<Pagination onConfirm={handleDisableAll}
current={currentPage} okType={'danger'}
total={total} position={'topRight'}
pageSize={pageSize} >
showSizeChanger={false} <Button
showQuickJumper size='small'
size='small' type='danger'
onChange={handlePageChange} loading={operationLoading.disable_all}
showTotal={(total, range) => >
t('第 {{current}} / {{total}} 页', { {t('禁用全部')}
current: currentPage, </Button>
total: totalPages </Popconfirm>
}) )}
} <Popconfirm
/> title={t('确定要删除所有已自动禁用的密钥吗?')}
</div> content={t('此操作不可撤销,将永久删除已自动禁用的密钥')}
</div> onConfirm={handleDeleteDisabledKeys}
okType={'danger'}
position={'topRight'}
>
<Button
size='small'
type='warning'
loading={operationLoading.delete_disabled}
>
{t('删除自动禁用密钥')}
</Button>
</Popconfirm>
</Space>
</Col>
</Row>
)} )}
</div> columns={columns}
) : ( dataSource={keyStatusList}
!loading && ( pagination={{
<Empty currentPage: currentPage,
image={Empty.PRESENTED_IMAGE_SIMPLE} pageSize: pageSize,
title={t('暂无密钥数据')} total: total,
description={t('请检查渠道配置或刷新重试')} showSizeChanger: true,
/> showQuickJumper: true,
) pageSizeOptions: ['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={
<Empty
image={<IllustrationNoResult style={{ width: 140, height: 140 }} />}
darkModeImage={<IllustrationNoResultDark style={{ width: 140, height: 140 }} />}
title={t('暂无密钥数据')}
description={t('请检查渠道配置或刷新重试')}
style={{ padding: 30 }}
/>
}
/>
</Card>
</Spin> </Spin>
</div> </div>
</div> </div>

View File

@@ -1890,5 +1890,22 @@
"未知供应商": "Unknown", "未知供应商": "Unknown",
"共 {{count}} 个模型": "{{count}} models", "共 {{count}} 个模型": "{{count}} models",
"倍率信息": "Ratio information", "倍率信息": "Ratio information",
"倍率是用于系统计算不同模型的最终价格用的,如果您不理解倍率,请忽略": "The ratio is used to calculate the final price of different models in the system. If you do not understand the ratio, please ignore it." "倍率是用于系统计算不同模型的最终价格用的,如果您不理解倍率,请忽略": "The ratio is used to calculate the final price of different models in the system. If you do not understand the ratio, please ignore it.",
"多密钥管理": "Multi-key management",
"总密钥数": "Total key count",
"随机模式": "Random mode",
"轮询模式": "Polling mode",
"手动禁用": "Manually disabled",
"自动禁用": "Auto disabled",
"暂无密钥数据": "No key data",
"请检查渠道配置或刷新重试": "Please check the channel configuration or refresh and try again",
"全部状态": "All status",
"索引": "Index",
"禁用原因": "Disable reason",
"禁用时间": "Disable time",
"确定要启用所有密钥吗?": "Are you sure you want to enable all keys?",
"确定要禁用所有的密钥吗?": "Are you sure you want to disable all keys?",
"确定要删除所有已自动禁用的密钥吗?": "Are you sure you want to delete all automatically disabled keys?",
"此操作不可撤销,将永久删除已自动禁用的密钥": "This operation cannot be undone, and all automatically disabled keys will be permanently deleted.",
"删除自动禁用密钥": "Delete auto disabled keys"
} }