'use client' import Select from '@/components/ui/Select' import { useCallback, useEffect, useState } from 'react' import { parseFetchError } from '@/lib/error-handler' import { BellRing, Copy, Eye, EyeOff, KeyRound, ShieldCheck, Webhook, Cpu } from 'lucide-react' import NotificationCenterPanel from '@/components/admin/NotificationCenterPanel' import AiModelManagementPanel from '@/components/admin/AiModelManagementPanel' import { useConfirmActionDialog } from '@/components/ConfirmActionDialog' interface SuperAdmin { id: string email: string username: string realName: string | null department: string role: string createdAt: string } interface ApiKey { id: string name: string key: string canReveal: boolean isActive: boolean createdBy: string lastUsedAt: string | null createdAt: string } type NotificationAuthType = 'none' | 'bearer' | 'header' interface ExternalNotificationTarget { id: string name: string callbackUrl: string isActive: boolean authType: NotificationAuthType authConfig: Record authConfigured?: boolean authMasked?: string authSummary?: string eventTypes: string[] createdAt: string updatedAt: string } interface ExternalNotificationDelivery { id: string targetId: string targetName: string eventType: string materialId: string materialType: string taskNo: string | null status: string attemptCount: number maxAttempts: number nextRetryAt: string | null lastStatusCode: number | null lastResponseBody: string | null lastError: string | null createdAt: string updatedAt: string completedAt: string | null } interface NotificationTargetFormState { name: string callbackUrl: string authType: NotificationAuthType token: string headerName: string headerValue: string eventTypes: string[] isActive: boolean } const NOTIFICATION_EVENT_OPTIONS = [ { value: 'task-detail.full-set.ready', label: '任务-详情全套入库完成', description: '整套素材完成后推送成套资源信息。', }, { value: 'task-detail.barcode-images.updated', label: '任务-详情条码图片更新', description: '带产品条码的 SKU 预览图新增或更新后推送条码清单。', }, ] const EMPTY_NOTIFICATION_TARGET_FORM: NotificationTargetFormState = { name: '', callbackUrl: '', authType: 'none', token: '', headerName: '', headerValue: '', eventTypes: ['task-detail.full-set.ready'], isActive: true, } const NOTIFICATION_DELIVERY_STATUS_OPTIONS = [ { value: 'all', label: '全部状态' }, { value: 'pending', label: '待发送' }, { value: 'processing', label: '发送中' }, { value: 'retrying', label: '重试中' }, { value: 'succeeded', label: '已成功' }, { value: 'exhausted', label: '待人工处理' }, ] function formatDateTime(value: string | null) { if (!value) { return '无' } return new Date(value).toLocaleString('zh-CN') } function getNotificationTargetAuthSummary(target: ExternalNotificationTarget) { if (target.authSummary || target.authMasked) { return target.authSummary || target.authMasked || '已安全配置' } if (target.authType === 'bearer') { return target.authConfigured === false ? 'Bearer · 未配置' : 'Bearer · 已安全配置' } if (target.authType === 'header') { const headerName = target.authConfig.headerName || '自定义请求头' return `${headerName} · ${target.authConfigured === false ? '未配置' : '已安全配置'}` } return '无鉴权' } function getNotificationDeliveryStatusLabel(status: string) { switch (status) { case 'pending': return '待发送' case 'processing': return '发送中' case 'retrying': return '重试中' case 'succeeded': return '已成功' case 'exhausted': return '待人工处理' default: return status } } function getNotificationDeliveryStatusClassName(status: string) { switch (status) { case 'succeeded': return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' case 'retrying': case 'processing': return 'bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-200' case 'exhausted': return 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-200' default: return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' } } interface SuperAdminsTabProps { showToast: (message: string, type?: 'success' | 'error') => void } type SuperAdminWorkspace = 'access' | 'notificationCenter' | 'outbound' | 'aiModels' const SUPER_ADMIN_WORKSPACES: Array<{ id: SuperAdminWorkspace label: string description: string icon: typeof ShieldCheck }> = [ { id: 'access', label: '权限与凭证', description: '超级管理员与素材平台 API Key', icon: ShieldCheck }, { id: 'notificationCenter', label: '统一通知中心', description: '项目、花名、测试与员工单聊', icon: BellRing }, { id: 'outbound', label: '素材外发', description: 'Webhook 目标与自动上架投递', icon: Webhook }, { id: 'aiModels', label: 'AI 模型接入', description: '上游连接、模型用途与默认模型', icon: Cpu }, ] export default function SuperAdminsTab({ showToast }: SuperAdminsTabProps) { const [activeWorkspace, setActiveWorkspace] = useState('notificationCenter') const [aiModelsState, setAiModelsState] = useState({ dirty: false, busy: false }) const { confirmAction, confirmActionDialog } = useConfirmActionDialog() const [superAdmins, setSuperAdmins] = useState([]) const [loading, setLoading] = useState(true) const [count, setCount] = useState(0) const [canAdd, setCanAdd] = useState(false) const [addingUserId, setAddingUserId] = useState('') const [adding, setAdding] = useState(false) const [removingUserId, setRemovingUserId] = useState(null) // API Keys 相关状态 const [apiKeys, setApiKeys] = useState([]) const [apiKeysLoading, setApiKeysLoading] = useState(true) const [showCreateModal, setShowCreateModal] = useState(false) const [newKeyName, setNewKeyName] = useState('') const [createdKey, setCreatedKey] = useState(null) const [revealedApiKeys, setRevealedApiKeys] = useState>({}) const [revealingApiKeyId, setRevealingApiKeyId] = useState(null) const [notificationTargets, setNotificationTargets] = useState([]) const [notificationTargetsLoading, setNotificationTargetsLoading] = useState(true) const [notificationTargetForm, setNotificationTargetForm] = useState( EMPTY_NOTIFICATION_TARGET_FORM, ) const [editingNotificationTargetId, setEditingNotificationTargetId] = useState(null) const [savingNotificationTarget, setSavingNotificationTarget] = useState(false) const [togglingNotificationTargetId, setTogglingNotificationTargetId] = useState(null) const [notificationDeliveries, setNotificationDeliveries] = useState([]) const [notificationDeliveriesLoading, setNotificationDeliveriesLoading] = useState(true) const [notificationDeliveryStatusFilter, setNotificationDeliveryStatusFilter] = useState('all') const [retryingNotificationDeliveryId, setRetryingNotificationDeliveryId] = useState(null) const changeWorkspace = async (next: SuperAdminWorkspace) => { if (next === activeWorkspace || (activeWorkspace === 'aiModels' && aiModelsState.busy)) return if (activeWorkspace === 'aiModels' && aiModelsState.dirty) { const result = await confirmAction({ title: '离开模型接入?', description: '当前修改尚未保存,离开后将丢失。', confirmLabel: '放弃修改并离开', cancelLabel: '继续编辑', tone: 'danger' }) if (!result.confirmed) return } setActiveWorkspace(next) } // 加载超级管理员列表 const loadSuperAdmins = useCallback(async () => { try { const response = await fetch('/api/admin/super-admins') if (response.ok) { const data = await response.json() setSuperAdmins(data.superAdmins) setCount(data.count) setCanAdd(data.canAdd) } else { showToast('加载超级管理员列表失败', 'error') } } catch (error) { console.error('Load super admins error:', error) showToast('加载超级管理员列表失败', 'error') } finally { setLoading(false) } }, [showToast]) // 添加超级管理员 const handleAddSuperAdmin = async () => { if (!addingUserId.trim()) { showToast('请输入用户ID', 'error') return } setAdding(true) try { const response = await fetch('/api/admin/super-admins', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: addingUserId.trim() }), }) if (response.ok) { showToast('超级管理员添加成功', 'success') setAddingUserId('') await loadSuperAdmins() } else { const data = await response.json() showToast(data.error || '添加失败', 'error') } } catch (error) { console.error('Add super admin error:', error) showToast('添加失败', 'error') } finally { setAdding(false) } } // 移除超级管理员 const handleRemoveSuperAdmin = async (userId: string) => { if (!confirm('确定要移除该超级管理员权限吗?')) { return } setRemovingUserId(userId) try { const response = await fetch(`/api/admin/super-admins?userId=${userId}`, { method: 'DELETE', }) if (response.ok) { showToast('超级管理员权限已移除', 'success') await loadSuperAdmins() } else { const data = await response.json() showToast(data.error || '移除失败', 'error') } } catch (error) { console.error('Remove super admin error:', error) showToast('移除失败', 'error') } finally { setRemovingUserId(null) } } // 加载 API Keys const fetchApiKeys = useCallback(async () => { try { setApiKeysLoading(true) const response = await fetch('/api/admin/api-keys') if (response.ok) { const data = await response.json() setApiKeys(data.data || []) } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('加载 API Keys 失败:', error) const errorMessage = error instanceof Error ? error.message : '加载失败' showToast(errorMessage, 'error') } finally { setApiKeysLoading(false) } }, [showToast]) const resetNotificationTargetForm = () => { setEditingNotificationTargetId(null) setNotificationTargetForm(EMPTY_NOTIFICATION_TARGET_FORM) } const fetchNotificationTargets = useCallback(async () => { try { setNotificationTargetsLoading(true) const response = await fetch('/api/admin/external-notification-targets') if (response.ok) { const data = await response.json() setNotificationTargets(data.data || []) } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('加载自动上架通知目标失败:', error) const errorMessage = error instanceof Error ? error.message : '加载失败' showToast(errorMessage, 'error') } finally { setNotificationTargetsLoading(false) } }, [showToast]) const fetchNotificationDeliveries = useCallback(async (status: string) => { try { setNotificationDeliveriesLoading(true) const searchParams = new URLSearchParams() if (status && status !== 'all') { searchParams.set('status', status) } searchParams.set('limit', '50') const query = searchParams.toString() const response = await fetch( `/api/admin/external-notification-deliveries${query ? `?${query}` : ''}`, ) if (response.ok) { const data = await response.json() setNotificationDeliveries(data.data || []) } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('加载自动上架投递记录失败:', error) const errorMessage = error instanceof Error ? error.message : '加载失败' showToast(errorMessage, 'error') } finally { setNotificationDeliveriesLoading(false) } }, [showToast]) const handleSaveNotificationTarget = async () => { if (!notificationTargetForm.name.trim()) { showToast('请填写目标系统名称', 'error') return } if (!notificationTargetForm.callbackUrl.trim()) { showToast('请填写回调地址', 'error') return } if ( notificationTargetForm.authType === 'bearer' && !notificationTargetForm.token.trim() && !editingNotificationTargetId ) { showToast('请选择 Bearer 鉴权时必须填写 token', 'error') return } if ( notificationTargetForm.authType === 'header' && (!notificationTargetForm.headerName.trim() || (!notificationTargetForm.headerValue.trim() && !editingNotificationTargetId)) ) { showToast('自定义 Header 鉴权需要填写请求头名称和值', 'error') return } if (notificationTargetForm.eventTypes.length === 0) { showToast('请至少选择 1 个通知事件', 'error') return } setSavingNotificationTarget(true) try { const authConfig = notificationTargetForm.authType === 'bearer' ? notificationTargetForm.token.trim() ? { token: notificationTargetForm.token.trim() } : undefined : notificationTargetForm.authType === 'header' ? notificationTargetForm.headerValue.trim() ? { headerName: notificationTargetForm.headerName.trim(), headerValue: notificationTargetForm.headerValue.trim(), } : undefined : {} const payload = { name: notificationTargetForm.name.trim(), callbackUrl: notificationTargetForm.callbackUrl.trim(), isActive: notificationTargetForm.isActive, authType: notificationTargetForm.authType, ...(authConfig === undefined ? {} : { authConfig }), eventTypes: notificationTargetForm.eventTypes, } const response = await fetch( editingNotificationTargetId ? `/api/admin/external-notification-targets/${editingNotificationTargetId}` : '/api/admin/external-notification-targets', { method: editingNotificationTargetId ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, ) if (response.ok) { showToast(editingNotificationTargetId ? '通知目标已更新' : '通知目标已创建', 'success') resetNotificationTargetForm() await fetchNotificationTargets() } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('保存自动上架通知目标失败:', error) const errorMessage = error instanceof Error ? error.message : '保存失败' showToast(errorMessage, 'error') } finally { setSavingNotificationTarget(false) } } const handleEditNotificationTarget = (target: ExternalNotificationTarget) => { setEditingNotificationTargetId(target.id) setNotificationTargetForm({ name: target.name, callbackUrl: target.callbackUrl, authType: target.authType, token: '', headerName: target.authConfig.headerName || '', headerValue: '', eventTypes: target.eventTypes?.length > 0 ? target.eventTypes : ['task-detail.full-set.ready'], isActive: target.isActive, }) } const handleToggleNotificationTargetActive = async (target: ExternalNotificationTarget) => { setTogglingNotificationTargetId(target.id) try { const response = await fetch(`/api/admin/external-notification-targets/${target.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ isActive: !target.isActive, }), }) if (response.ok) { showToast(target.isActive ? '通知目标已停用' : '通知目标已启用', 'success') await fetchNotificationTargets() } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('切换通知目标状态失败:', error) const errorMessage = error instanceof Error ? error.message : '更新失败' showToast(errorMessage, 'error') } finally { setTogglingNotificationTargetId(null) } } const handleRetryNotificationDelivery = async (deliveryId: string) => { setRetryingNotificationDeliveryId(deliveryId) try { const response = await fetch(`/api/admin/external-notification-deliveries/${deliveryId}/retry`, { method: 'POST', }) if (response.ok) { showToast('补发任务已重新入队', 'success') await fetchNotificationDeliveries(notificationDeliveryStatusFilter) } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('手工补发失败:', error) const errorMessage = error instanceof Error ? error.message : '补发失败' showToast(errorMessage, 'error') } finally { setRetryingNotificationDeliveryId(null) } } // 创建 API Key const handleCreateApiKey = async () => { if (!newKeyName.trim()) { showToast('请输入密钥名称', 'error') return } try { const response = await fetch('/api/admin/api-keys', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newKeyName }), }) if (response.ok) { const data = await response.json() setCreatedKey(data.data.key) setNewKeyName('') showToast('API Key 创建成功', 'success') fetchApiKeys() } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('创建失败:', error) const errorMessage = error instanceof Error ? error.message : '创建失败' showToast(errorMessage, 'error') } } // 切换 API Key 状态 const handleToggleApiKeyActive = async (id: string, isActive: boolean) => { try { const response = await fetch(`/api/admin/api-keys/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ isActive: !isActive }), }) if (response.ok) { showToast(isActive ? 'API Key 已禁用' : 'API Key 已启用', 'success') fetchApiKeys() } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('更新失败:', error) const errorMessage = error instanceof Error ? error.message : '更新失败' showToast(errorMessage, 'error') } } const handleRevealApiKey = async (apiKey: ApiKey) => { if (revealedApiKeys[apiKey.id]) { setRevealedApiKeys((current) => { const next = { ...current } delete next[apiKey.id] return next }) return } if (!apiKey.canReveal) { showToast('该历史 API Key 只保存了哈希,请创建新 Key 后逐步替换', 'error') return } try { setRevealingApiKeyId(apiKey.id) const response = await fetch(`/api/admin/api-keys/${apiKey.id}/reveal`, { method: 'POST', cache: 'no-store', }) if (!response.ok) { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') return } const data = await response.json() setRevealedApiKeys((current) => ({ ...current, [apiKey.id]: data.data.key })) } catch (error) { console.error('查看 API Key 失败:', error) const errorMessage = error instanceof Error ? error.message : '查看失败' showToast(errorMessage, 'error') } finally { setRevealingApiKeyId(null) } } const handleCopyApiKey = async (apiKey: string) => { try { await navigator.clipboard.writeText(apiKey) showToast('API Key 已复制', 'success') } catch (error) { console.error('复制 API Key 失败:', error) showToast('复制失败,请手动选择密钥文本', 'error') } } // 删除 API Key const handleDeleteApiKey = async (id: string) => { if (!confirm('确定要删除此 API Key 吗?删除后无法恢复。')) return try { const response = await fetch(`/api/admin/api-keys/${id}`, { method: 'DELETE', }) if (response.ok) { showToast('API Key 已删除', 'success') setRevealedApiKeys((current) => { const next = { ...current } delete next[id] return next }) fetchApiKeys() } else { const errorData = await parseFetchError(response) showToast(errorData.message, 'error') } } catch (error) { console.error('删除失败:', error) const errorMessage = error instanceof Error ? error.message : '删除失败' showToast(errorMessage, 'error') } } useEffect(() => { void loadSuperAdmins() void fetchApiKeys() void fetchNotificationTargets() }, [fetchApiKeys, fetchNotificationTargets, loadSuperAdmins]) useEffect(() => { if (activeWorkspace !== 'access') { setRevealedApiKeys({}) setCreatedKey(null) setShowCreateModal(false) } }, [activeWorkspace]) useEffect(() => { void fetchNotificationDeliveries(notificationDeliveryStatusFilter) }, [fetchNotificationDeliveries, notificationDeliveryStatusFilter]) if (loading) { return (
) } return (
{confirmActionDialog}

超级管理员工作台

集中管理高权限账号、平台凭证、统一钉钉通知和素材外发。各分区权限均绑定当前项目的超级管理员身份。

{activeWorkspace === 'aiModels' && } {activeWorkspace === 'access' && (

当前 {count} / 4 个超级管理员{!canAdd ? ',已达到上限' : ''}。本区 API Key 仅属于素材平台外部接口。

{/* 添加超级管理员 */} {canAdd && (

添加超级管理员

setAddingUserId(e.target.value)} placeholder="输入用户ID" className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />

提示:只能添加管理员角色的用户,且最多4个超级管理员

)} {/* 超级管理员列表 */}

超级管理员列表

{superAdmins.length === 0 ? (
暂无超级管理员
) : (
{superAdmins.map((admin) => (
{admin.realName || admin.username} 超级管理员
{admin.email} {admin.department && ( · {admin.department} )}
创建时间:{new Date(admin.createdAt).toLocaleString('zh-CN')}
))}
)}
{/* 素材平台 API Key 管理 */}

素材平台 API Key

{apiKeysLoading ? (
加载中...
) : apiKeys.length === 0 ? (

暂无素材平台 API Key

点击上方按钮创建第一个 API Key

) : (
{apiKeys.map((key) => ( ))}
名称 密钥 状态 最后使用 创建时间 操作
{key.name}
{revealedApiKeys[key.id] || key.key} {revealedApiKeys[key.id] && ( )}
{!key.canReveal && (

历史 Key 无法恢复,需创建新 Key 替换

)}
{key.isActive ? '启用' : '禁用'} {key.lastUsedAt ? new Date(key.lastUsedAt).toLocaleString('zh-CN') : '从未使用'} {new Date(key.createdAt).toLocaleString('zh-CN')}
)}
)} {activeWorkspace === 'notificationCenter' && ( )} {activeWorkspace === 'outbound' && (

素材外发目标

配置素材库向其他业务系统发送 Webhook 的目标,与统一通知中心向员工发送钉钉单聊不同。

已配置目标

{notificationTargetsLoading ? (
加载中...
) : notificationTargets.length === 0 ? (
暂无通知目标
) : (
{notificationTargets.map((target) => (
{target.name} {target.isActive ? '启用中' : '已停用'}
{target.callbackUrl}
鉴权:{getNotificationTargetAuthSummary(target)}
{target.eventTypes.map((eventType) => ( {eventType === 'task-detail.full-set.ready' ? '自动上架平台 / 全套入库' : eventType === 'task-detail.barcode-images.updated' ? '产品平台 / 条码图片更新' : eventType} ))}
更新时间:{formatDateTime(target.updatedAt)}
))}
)}

{editingNotificationTargetId ? '编辑素材外发目标' : '新增素材回调'}

setNotificationTargetForm((current) => ({ ...current, name: e.target.value, })) } placeholder="例如:自动上架平台 / 产品平台" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
setNotificationTargetForm((current) => ({ ...current, callbackUrl: e.target.value, })) } placeholder="https://example.com/callback" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
{notificationTargetForm.authType === 'bearer' && (
setNotificationTargetForm((current) => ({ ...current, token: e.target.value, })) } placeholder="请输入 token" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
)} {notificationTargetForm.authType === 'header' && (
setNotificationTargetForm((current) => ({ ...current, headerName: e.target.value, })) } placeholder="例如:X-Signature" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
setNotificationTargetForm((current) => ({ ...current, headerValue: e.target.value, })) } placeholder="请输入请求头值" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
)}
订阅事件
{NOTIFICATION_EVENT_OPTIONS.map((eventOption) => ( ))}

自动上架投递记录

查看最近 50 条通知投递情况;失败或超上限的记录可以在这里手工补发。

{notificationDeliveriesLoading ? (
加载中...
) : notificationDeliveries.length === 0 ? (
当前筛选条件下暂无投递记录
) : (
{notificationDeliveries.map((delivery) => ( ))}
目标系统 素材 状态 尝试次数 最近结果 下一次重试 更新时间 操作
{delivery.targetName || '未知目标'}
{delivery.id}
{delivery.materialType}
materialId: {delivery.materialId}
taskNo: {delivery.taskNo || '无'}
{getNotificationDeliveryStatusLabel(delivery.status)} {delivery.attemptCount} / {delivery.maxAttempts}
{delivery.lastStatusCode ? `HTTP ${delivery.lastStatusCode}` : '暂无'}
{delivery.lastError || delivery.lastResponseBody || '无'}
{formatDateTime(delivery.nextRetryAt)} {formatDateTime(delivery.updatedAt)}
)}
)}
{/* 创建 API Key 模态框 */} {activeWorkspace === 'access' && showCreateModal && (

创建素材平台 API Key

{createdKey ? (

API Key 已创建,可复制给对应的外部系统:

{createdKey}

关闭后仍可在列表中由超级管理员按需查看,请只通过安全渠道发送给外部系统负责人。

) : (
setNewKeyName(e.target.value)} placeholder="例如:任务系统" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg mb-4 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" onKeyPress={(e) => { if (e.key === 'Enter') { handleCreateApiKey() } }} />
)}
)}
) }