import { useState, useEffect, useCallback } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Textarea } from '@/components/ui/textarea'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/dialog'; import { Plus, Trash2, Edit3, Key, Globe, Shield, Eye, EyeOff, RefreshCw, } from 'lucide-react'; import { credentialApi } from '@/services/api'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { toast } from 'sonner'; type CredentialType = 'ssh_key' | 'api_token' | 'password' | 'access_key'; const TYPE_CONFIG: Record = { ssh_key: { label: 'SSH 密钥', icon: Key, color: 'text-amber-400' }, api_token: { label: 'API Token', icon: Globe, color: 'text-blue-400' }, password: { label: '密码', icon: Shield, color: 'text-emerald-400' }, access_key: { label: 'AK密钥对', icon: Key, color: 'text-purple-400' }, }; interface CredentialItem { id: number; name: string; type: CredentialType; preview: string; description: string; created_at: string; updated_at: string; } const EMPTY_FORM = { name: '', type: 'password' as CredentialType, content: '', description: '' }; export function CredentialManager() { const confirm = useConfirm(); const [credentials, setCredentials] = useState([]); const [loading, setLoading] = useState(true); // 表单状态 const [formOpen, setFormOpen] = useState(false); const [editingId, setEditingId] = useState(null); const [formData, setFormData] = useState(EMPTY_FORM); const [saving, setSaving] = useState(false); const [showContent, setShowContent] = useState(false); const fetchList = useCallback(async () => { try { setLoading(true); const res: any = await credentialApi.list(); setCredentials(res?.list || []); } catch (e) { console.error('加载凭据列表失败:', e); } finally { setLoading(false); } }, []); useEffect(() => { fetchList(); }, [fetchList]); const openCreate = () => { setEditingId(null); setFormData(EMPTY_FORM); setShowContent(false); setFormOpen(true); }; const openEdit = (item: CredentialItem) => { setEditingId(item.id); setFormData({ name: item.name, type: item.type, content: '', // 编辑时空内容表示不更新 description: item.description || '', }); setShowContent(false); setFormOpen(true); }; const handleSave = async () => { if (!formData.name || (!editingId && !formData.content)) return; try { setSaving(true); if (editingId) { await credentialApi.update(editingId, { name: formData.name, type: formData.type, ...(formData.content ? { content: formData.content } : {}), description: formData.description, }); toast.success('凭据已更新'); } else { await credentialApi.create(formData); toast.success('凭据已创建'); } setFormOpen(false); fetchList(); } catch (e: any) { toast.error(e?.message || '操作失败'); } finally { setSaving(false); } }; const handleDelete = async (id: number) => { const item = credentials.find(c => c.id === id); const ok = await confirm({ title: '删除凭据', description: `确定删除「${item?.name || id}」吗?删除后无法恢复,关联的服务器将无法使用此凭据连接。`, confirmText: '删除', variant: 'destructive', }); if (!ok) return; try { await credentialApi.delete(id); toast.success('凭据已删除'); fetchList(); } catch (e) { toast.error('删除失败'); } }; const typeIcon = (type: CredentialType) => { const cfg = TYPE_CONFIG[type]; const Icon = cfg.icon; return ; }; const typeBadge = (type: CredentialType) => { const cfg = TYPE_CONFIG[type]; const bgMap: Record = { ssh_key: 'bg-amber-500/20 text-amber-400', api_token: 'bg-blue-500/20 text-blue-400', password: 'bg-emerald-500/20 text-emerald-400', access_key: 'bg-purple-500/20 text-purple-400', }; return ( {cfg.label} ); }; return (
{/* Header */}

凭证配置

SSH 密钥、API Token、密码、AK密钥对等敏感信息统一加密管理

{/* Table */} 名称 类型 内容预览 描述 更新时间 操作 {loading ? ( 加载中... ) : credentials.length === 0 ? ( 暂无凭据,点击「添加凭据」创建 ) : ( credentials.map((cred) => (
{typeIcon(cred.type)} {cred.name}
{typeBadge(cred.type)} {cred.preview || '****'} {cred.description || '-'} {cred.updated_at ? new Date(cred.updated_at).toLocaleDateString('zh-CN') : '-'}
)) )}
{/* Form Dialog */} {editingId ? '编辑凭据' : '添加凭据'} {editingId ? '编辑凭据信息,留空内容不更新' : '添加新的凭据'}
{/* 名称 */}
setFormData({ ...formData, name: e.target.value })} className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
{/* 类型 */}
{/* 内容 */}