/* * @Author: ZhaoYing * @Date: 2026-02-03 15:52:50 * @Last Modified by: ZhaoYing * @Last Modified time: 2026-02-03 15:52:50 */ import React, { useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, App, Dropdown, Flex } from 'antd'; import clsx from 'clsx'; import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons'; import copy from 'copy-to-clipboard' import type { MenuInfo } from 'rc-menu/lib/interface'; import type { ApiKey, ApiKeyModalRef } from './types'; import ApiKeyModal from './components/ApiKeyModal'; import ApiKeyDetailModal from './components/ApiKeyDetailModal'; import RbCard from '@/components/RbCard' import { getApiKeyListUrl, deleteApiKey } from '@/api/apiKey'; import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList' import { formatDateTime } from '@/utils/format'; import Tag from '@/components/Tag' import { maskApiKeys } from '@/utils/apiKeyReplacer'; import RbDescriptions from '@/components/RbDescriptions'; /** * API Key Management page component * Manages service API keys with CRUD operations */ const ApiKeyManagement: React.FC = () => { // Hooks const { t } = useTranslation(); const { modal, message } = App.useApp(); // Refs const apiKeyModalRef = useRef(null); const apiKeyDetailModalRef = useRef(null) const scrollListRef = useRef(null) /** * Refresh the API key list */ const refresh = () => { scrollListRef.current?.refresh(); } /** * Open modal to create or edit API key * @param item - Optional API key item for edit mode */ const handleEdit = (item?: ApiKey) => { apiKeyModalRef.current?.handleOpen(item); } /** * Open modal to view API key details * @param item - API key item to view */ const handleView = (item: ApiKey) => { apiKeyDetailModalRef.current?.handleOpen(item); } /** * Delete API key with confirmation * @param item - API key item to delete */ const handleDelete = (item: ApiKey) => { modal.confirm({ title: t('common.confirmDeleteDesc', { name: item.name }), okText: t('common.delete'), cancelText: t('common.cancel'), okType: 'danger', onOk: () => { deleteApiKey(item.id) .then(() => { refresh(); message.success(t('common.deleteSuccess')) }) } }) } /** * Copy content to clipboard * @param content - Content to copy */ const handleCopy = (content: string) => { copy(content) message.success(t('common.copySuccess')) } return ( <> ref={scrollListRef} url={getApiKeyListUrl} query={{ is_active: true, type: 'service' }} column={3} renderItem={(apiKeyItem) => { return ( {apiKeyItem.name} {apiKeyItem.scopes?.includes('memory') && {t('apiKey.memoryEngine')}} {apiKeyItem.scopes?.includes('rag') && {t('apiKey.knowledgeBase')}} {!apiKeyItem.scopes?.includes('memory') && !apiKeyItem.scopes?.includes('rag') &&
{t('apiKey.noScopes')}
}
, label: t('common.edit'), onClick: () => handleEdit(apiKeyItem), }, { key: 'view', icon:
, label: t('common.view'), onClick: () => handleView(apiKeyItem), }, { key: 'delete', danger: true, icon:
, label: t('common.delete'), onClick: () => handleDelete(apiKeyItem), }, ] }} placement="bottomRight" >
} isNeedTooltip={false} headerClassName="rb:min-h-[78px]!" > ({ key, label: t(`apiKey.${key}`), children: {key === 'created_at' ? formatDateTime(apiKeyItem[key], 'YYYY-MM-DD HH:mm:ss') : key === 'is_expired' ? {apiKeyItem[key] ? t('apiKey.inactive') : t('apiKey.active')} : String(apiKeyItem[key as keyof ApiKey]) } }))} /> {maskApiKeys(apiKeyItem.api_key)}
handleCopy(apiKeyItem.api_key)} className="rb:cursor-pointer rb:rounded-md rb:size-6 rb:bg-[url('@/assets/images/common/copy_dark.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat" style={{ backgroundColor: 'rgba(0,0,0,0.08)' }}>
); }} /> ); }; export default ApiKeyManagement;