import React, { useState, useEffect, useMemo } from 'react'; import { RefreshCcw, ChevronDown, User, Globe, Link, Clipboard, ShieldAlert, Loader2 } from 'lucide-react'; import { Credential, UserGroup, BrowserGroup } from '../types'; import { formatDate } from '../utils'; interface CredentialsTabProps { hostname: string; } export default function CredentialsTab({ hostname }: CredentialsTabProps) { const [credentials, setCredentials] = useState([]); const [loadingCredentials, setLoadingCredentials] = useState(false); const [expandedUsers, setExpandedUsers] = useState([]); const [expandedBrowsers, setExpandedBrowsers] = useState([]); const [expandedCredentials, setExpandedCredentials] = useState([]); const [revealedPasswords, setRevealedPasswords] = useState([]); const fetchCredentials = async () => { try { setLoadingCredentials(true); const response = await fetch(`/hosts/${hostname}/credentials`); if (!response.ok) throw new Error('获取凭据数据失败'); const data = await response.json(); setCredentials(data); if (data.length > 0) { const firstUser = data[0].username; if (!expandedUsers.includes(firstUser)) { setExpandedUsers([firstUser]); } } } catch (error) { console.error('获取凭据数据失败:', error); } finally { setLoadingCredentials(false); } }; useEffect(() => { fetchCredentials(); }, [hostname]); const credentialsByUser = useMemo(() => { const userMap = new Map(); credentials.forEach(cred => { if (!userMap.has(cred.username)) { userMap.set(cred.username, []); } userMap.get(cred.username)!.push(cred); }); const result: UserGroup[] = []; userMap.forEach((userCreds, username) => { const latestSyncTime = userCreds.reduce((latest, cred) => { const credTime = new Date(cred.lastSyncTime); return credTime > latest ? credTime : latest; }, new Date(0)).toISOString(); const browserMap = new Map(); userCreds.forEach(cred => { if (!browserMap.has(cred.browser)) { browserMap.set(cred.browser, []); } browserMap.get(cred.browser)!.push(cred); }); const browsers: BrowserGroup[] = []; browserMap.forEach((browserCreds, browserName) => { browsers.push({ name: browserName, credentials: browserCreds }); }); result.push({ username, browsers, total: userCreds.length, lastSyncTime: latestSyncTime }); }); return result; }, [credentials]); const toggleUserExpanded = (username: string) => { if (expandedUsers.includes(username)) { setExpandedUsers(expandedUsers.filter(u => u !== username)); } else { setExpandedUsers([...expandedUsers, username]); } }; const toggleBrowserExpanded = (browserKey: string) => { if (expandedBrowsers.includes(browserKey)) { setExpandedBrowsers(expandedBrowsers.filter(b => b !== browserKey)); } else { setExpandedBrowsers([...expandedBrowsers, browserKey]); } }; const toggleCredentialExpanded = (credId: string) => { if (expandedCredentials.includes(credId)) { setExpandedCredentials(expandedCredentials.filter(c => c !== credId)); } else { setExpandedCredentials([...expandedCredentials, credId]); } }; const revealPassword = (passwordKey: string) => { if (!revealedPasswords.includes(passwordKey)) { setRevealedPasswords([...revealedPasswords, passwordKey]); } }; const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); } catch (err) { console.error('复制失败:', err); } }; return (

凭据信息

{loadingCredentials ? (

加载凭据信息...

) : credentialsByUser.length === 0 ? (

没有找到任何凭据信息

可能是该主机尚未上报凭据数据

) : (
{credentialsByUser.map((userGroup, index) => (
toggleUserExpanded(userGroup.username)} >

{userGroup.username}

({userGroup.browsers.length} 个浏览器, {userGroup.total} 个凭据)
{userGroup.lastSyncTime ? `最后同步: ${formatDate(userGroup.lastSyncTime, 'short')}` : '未同步'}
{expandedUsers.includes(userGroup.username) && (
{userGroup.browsers.map((browser) => (
toggleBrowserExpanded(`${userGroup.username}-${browser.name}`)} >
{browser.name} ({browser.credentials.length} 个站点)
{expandedBrowsers.includes(`${userGroup.username}-${browser.name}`) && (
{browser.credentials.map((cred) => { const credentialId = cred._id || cred.id || `${userGroup.username}-${browser.name}-${cred.url}-${cred.login}`; const isExpanded = expandedCredentials.includes(credentialId); return (
toggleCredentialExpanded(credentialId)} >
{cred.url}
{isExpanded && (
用户名: {cred.login}
密码历史: {cred.passwords.length} 条记录
{cred.passwords.map((pwd, pwdIndex) => { const pwdKey = `${credentialId}-${pwdIndex}`; const revealed = revealedPasswords.includes(pwdKey); return (
{formatDate(pwd.timestamp, 'short')}
revealed ? null : revealPassword(pwdKey)} > {revealed ? pwd.value : '••••••••'}
); })}
)}
); })}
)}
))}
)}
))}
)}
); }