'use client'; import * as React from 'react'; import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { Copy, Trash2, Plus } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Switch } from '@/components/ui/switch'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogPortal, AlertDialogOverlay, } from '@/components/ui/alert-dialog'; import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; import { backendClient } from '@/app/infra/http'; interface ApiKey { id: number; name: string; key: string; description: string; created_at: string; } interface Webhook { id: number; name: string; url: string; description: string; enabled: boolean; created_at: string; } interface ApiIntegrationDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } export default function ApiIntegrationDialog({ open, onOpenChange, }: ApiIntegrationDialogProps) { const { t } = useTranslation(); const [activeTab, setActiveTab] = useState('apikeys'); const [apiKeys, setApiKeys] = useState([]); const [webhooks, setWebhooks] = useState([]); const [loading, setLoading] = useState(false); const [showCreateDialog, setShowCreateDialog] = useState(false); const [newKeyName, setNewKeyName] = useState(''); const [newKeyDescription, setNewKeyDescription] = useState(''); const [createdKey, setCreatedKey] = useState(null); const [deleteKeyId, setDeleteKeyId] = useState(null); // Webhook state const [showCreateWebhookDialog, setShowCreateWebhookDialog] = useState(false); const [newWebhookName, setNewWebhookName] = useState(''); const [newWebhookUrl, setNewWebhookUrl] = useState(''); const [newWebhookDescription, setNewWebhookDescription] = useState(''); const [newWebhookEnabled, setNewWebhookEnabled] = useState(true); const [deleteWebhookId, setDeleteWebhookId] = useState(null); // 清理 body 样式,防止对话框关闭后页面无法交互 useEffect(() => { if (!deleteKeyId && !deleteWebhookId) { const cleanup = () => { document.body.style.removeProperty('pointer-events'); }; cleanup(); const timer = setTimeout(cleanup, 100); return () => clearTimeout(timer); } }, [deleteKeyId, deleteWebhookId]); useEffect(() => { if (open) { loadApiKeys(); loadWebhooks(); } }, [open]); const loadApiKeys = async () => { setLoading(true); try { const response = (await backendClient.get('/api/v1/apikeys')) as { keys: ApiKey[]; }; setApiKeys(response.keys || []); } catch (error) { toast.error(`Failed to load API keys: ${error}`); } finally { setLoading(false); } }; const handleCreateApiKey = async () => { if (!newKeyName.trim()) { toast.error(t('common.apiKeyNameRequired')); return; } try { const response = (await backendClient.post('/api/v1/apikeys', { name: newKeyName, description: newKeyDescription, })) as { key: ApiKey }; setCreatedKey(response.key); toast.success(t('common.apiKeyCreated')); setNewKeyName(''); setNewKeyDescription(''); setShowCreateDialog(false); loadApiKeys(); } catch (error) { toast.error(`Failed to create API key: ${error}`); } }; const handleDeleteApiKey = async (keyId: number) => { try { await backendClient.delete(`/api/v1/apikeys/${keyId}`); toast.success(t('common.apiKeyDeleted')); loadApiKeys(); setDeleteKeyId(null); } catch (error) { toast.error(`Failed to delete API key: ${error}`); } }; const handleCopyKey = (key: string) => { navigator.clipboard.writeText(key); toast.success(t('common.apiKeyCopied')); }; const maskApiKey = (key: string) => { if (key.length <= 8) return key; return `${key.substring(0, 8)}...${key.substring(key.length - 4)}`; }; // Webhook methods const loadWebhooks = async () => { setLoading(true); try { const response = (await backendClient.get('/api/v1/webhooks')) as { webhooks: Webhook[]; }; setWebhooks(response.webhooks || []); } catch (error) { toast.error(`Failed to load webhooks: ${error}`); } finally { setLoading(false); } }; const handleCreateWebhook = async () => { if (!newWebhookName.trim()) { toast.error(t('common.webhookNameRequired')); return; } if (!newWebhookUrl.trim()) { toast.error(t('common.webhookUrlRequired')); return; } try { await backendClient.post('/api/v1/webhooks', { name: newWebhookName, url: newWebhookUrl, description: newWebhookDescription, enabled: newWebhookEnabled, }); toast.success(t('common.webhookCreated')); setNewWebhookName(''); setNewWebhookUrl(''); setNewWebhookDescription(''); setNewWebhookEnabled(true); setShowCreateWebhookDialog(false); loadWebhooks(); } catch (error) { toast.error(`Failed to create webhook: ${error}`); } }; const handleDeleteWebhook = async (webhookId: number) => { try { await backendClient.delete(`/api/v1/webhooks/${webhookId}`); toast.success(t('common.webhookDeleted')); loadWebhooks(); setDeleteWebhookId(null); } catch (error) { toast.error(`Failed to delete webhook: ${error}`); } }; const handleToggleWebhook = async (webhook: Webhook) => { try { await backendClient.put(`/api/v1/webhooks/${webhook.id}`, { enabled: !webhook.enabled, }); loadWebhooks(); } catch (error) { toast.error(`Failed to update webhook: ${error}`); } }; return ( <> { // 如果删除确认框是打开的,不允许关闭主对话框 if (!newOpen && (deleteKeyId || deleteWebhookId)) { return; } onOpenChange(newOpen); }} > {t('common.manageApiIntegration')} {t('common.apiKeys')} {t('common.webhooks')} {/* API Keys Tab */}
{t('common.apiKeyHint')}
{loading ? (
{t('common.loading')}
) : apiKeys.length === 0 ? (
{t('common.noApiKeys')}
) : (
{t('common.name')} {t('common.apiKeyValue')} {t('common.actions')} {apiKeys.map((key) => (
{key.name}
{key.description && (
{key.description}
)}
{maskApiKey(key.key)}
))}
)}
{/* Webhooks Tab */}
{t('common.webhookHint')}
{loading ? (
{t('common.loading')}
) : webhooks.length === 0 ? (
{t('common.noWebhooks')}
) : (
{t('common.name')} {t('common.webhookUrl')} {t('common.webhookEnabled')} {t('common.actions')} {webhooks.map((webhook) => (
{webhook.name}
{webhook.description && (
{webhook.description}
)}
{webhook.url}
handleToggleWebhook(webhook) } />
))}
)}
{/* Create API Key Dialog */} {t('common.createApiKey')}
setNewKeyName(e.target.value)} placeholder={t('common.name')} className="mt-1" />
setNewKeyDescription(e.target.value)} placeholder={t('common.description')} className="mt-1" />
{/* Show Created Key Dialog */} setCreatedKey(null)}> {t('common.apiKeyCreated')} {t('common.apiKeyCreatedMessage')}
{/* Create Webhook Dialog */} {t('common.createWebhook')}
setNewWebhookName(e.target.value)} placeholder={t('common.webhookName')} className="mt-1" />
setNewWebhookUrl(e.target.value)} placeholder="https://example.com/webhook" className="mt-1" />
setNewWebhookDescription(e.target.value)} placeholder={t('common.description')} className="mt-1" />
{/* Delete API Key Confirmation Dialog */} setCreatedKey(null)}> {t('common.apiKeyCreated')} {t('common.apiKeyCreatedMessage')}
{/* Delete Confirmation Dialog */} setDeleteKeyId(null)} /> setDeleteKeyId(null)} > {t('common.confirmDelete')} {t('common.apiKeyDeleteConfirm')} setDeleteKeyId(null)}> {t('common.cancel')} deleteKeyId && handleDeleteApiKey(deleteKeyId)} > {t('common.delete')} {/* Delete Webhook Confirmation Dialog */} setDeleteWebhookId(null)} /> setDeleteWebhookId(null)} > {t('common.confirmDelete')} {t('common.webhookDeleteConfirm')} setDeleteWebhookId(null)}> {t('common.cancel')} deleteWebhookId && handleDeleteWebhook(deleteWebhookId) } > {t('common.delete')} ); }