/** * Account Page * Cyberpunk Terminal Aesthetic */ import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { Textarea } from "@/components/ui/textarea"; import { User, Mail, Phone, Shield, Calendar, Save, KeyRound, LogOut, UserPlus, GitBranch, Terminal, Key, Copy, Trash2, CheckCircle2, ServerCrash } from "lucide-react"; import { apiClient } from "@/shared/api/serverClient"; import { toast } from "sonner"; import type { Profile } from "@/shared/types"; import { generateSSHKey, getSSHKey, deleteSSHKey, testSSHKey, clearKnownHosts } from "@/shared/api/sshKeys"; export default function Account() { const navigate = useNavigate(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [showLogoutDialog, setShowLogoutDialog] = useState(false); const [form, setForm] = useState({ full_name: "", phone: "", github_username: "", gitlab_username: "", }); const [passwordForm, setPasswordForm] = useState({ current_password: "", new_password: "", confirm_password: "", }); const [changingPassword, setChangingPassword] = useState(false); // SSH Key states const [sshKey, setSSHKey] = useState<{ has_key: boolean; public_key?: string; fingerprint?: string }>({ has_key: false }); const [generatingKey, setGeneratingKey] = useState(false); const [deletingKey, setDeletingKey] = useState(false); const [clearingKnownHosts, setClearingKnownHosts] = useState(false); const [testingKey, setTestingKey] = useState(false); const [testRepoUrl, setTestRepoUrl] = useState(""); const [showDeleteKeyDialog, setShowDeleteKeyDialog] = useState(false); useEffect(() => { loadProfile(); loadSSHKey(); }, []); const loadProfile = async () => { try { setLoading(true); const res = await apiClient.get('/users/me'); setProfile(res.data); setForm({ full_name: res.data.full_name || "", phone: res.data.phone || "", github_username: res.data.github_username || "", gitlab_username: res.data.gitlab_username || "", }); } catch (error) { console.error('Failed to load profile:', error); toast.error("加载账号信息失败"); } finally { setLoading(false); } }; const loadSSHKey = async () => { try { const data = await getSSHKey(); setSSHKey(data); } catch (error) { console.error('Failed to load SSH key:', error); } }; const handleGenerateSSHKey = async () => { try { setGeneratingKey(true); const data = await generateSSHKey(); setSSHKey({ has_key: true, public_key: data.public_key, fingerprint: data.fingerprint }); toast.success(data.message); } catch (error: any) { console.error('Failed to generate SSH key:', error); toast.error(error.response?.data?.detail || "生成SSH密钥失败"); } finally { setGeneratingKey(false); } }; const handleDeleteSSHKey = async () => { try { setDeletingKey(true); await deleteSSHKey(); setSSHKey({ has_key: false }); toast.success("SSH密钥已删除"); setShowDeleteKeyDialog(false); } catch (error: any) { console.error('Failed to delete SSH key:', error); toast.error(error.response?.data?.detail || "删除SSH密钥失败"); } finally { setDeletingKey(false); } }; const handleTestSSHKey = async () => { if (!testRepoUrl) { toast.error("请输入仓库URL"); return; } try { setTestingKey(true); const result = await testSSHKey(testRepoUrl); if (result.success) { toast.success("SSH连接测试成功"); // 在控制台输出详细信息 if (result.output) { console.log("SSH测试输出:", result.output); } } else { // 显示详细的错误信息 toast.error(result.message || "SSH连接测试失败", { description: result.output ? `详情: ${result.output.substring(0, 100)}...` : undefined, duration: 5000, }); // 在控制台输出完整错误信息 if (result.output) { console.error("SSH测试失败:", result.output); } } } catch (error: any) { console.error('Failed to test SSH key:', error); toast.error(error.response?.data?.detail || "测试SSH密钥失败"); } finally { setTestingKey(false); } }; const handleClearKnownHosts = async () => { try { setClearingKnownHosts(true); const result = await clearKnownHosts(); if (result.success) { toast.success(result.message || "known_hosts已清理"); } else { toast.error("清理known_hosts失败"); } } catch (error: any) { console.error('Failed to clear known_hosts:', error); toast.error(error.response?.data?.detail || "清理known_hosts失败"); } finally { setClearingKnownHosts(false); } }; const handleCopyPublicKey = () => { if (sshKey.public_key) { navigator.clipboard.writeText(sshKey.public_key); toast.success("公钥已复制到剪贴板"); } }; const handleSave = async () => { try { setSaving(true); const res = await apiClient.put('/users/me', form); setProfile(res.data); toast.success("账号信息已更新"); } catch (error) { console.error('Failed to update profile:', error); toast.error("更新失败"); } finally { setSaving(false); } }; const handleChangePassword = async () => { if (!passwordForm.new_password || !passwordForm.confirm_password) { toast.error("请填写新密码"); return; } if (passwordForm.new_password !== passwordForm.confirm_password) { toast.error("两次输入的密码不一致"); return; } if (passwordForm.new_password.length < 6) { toast.error("密码长度至少6位"); return; } try { setChangingPassword(true); await apiClient.put('/users/me', { password: passwordForm.new_password }); toast.success("密码已更新"); setPasswordForm({ current_password: "", new_password: "", confirm_password: "" }); } catch (error) { console.error('Failed to change password:', error); toast.error("密码更新失败"); } finally { setChangingPassword(false); } }; const formatDate = (dateString?: string) => { if (!dateString) return "-"; return new Date(dateString).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }); }; const getInitials = (name?: string, email?: string) => { if (name) return name.charAt(0).toUpperCase(); if (email) return email.charAt(0).toUpperCase(); return "U"; }; const handleLogout = () => { localStorage.removeItem('access_token'); toast.success("已退出登录"); navigate('/login'); }; const handleSwitchAccount = () => { localStorage.removeItem('access_token'); navigate('/login'); }; if (loading) { return (

加载中...

); } return (
{/* Grid background */}
{/* Profile Card */}

用户信息

{getInitials(profile?.full_name, profile?.email)}

{profile?.full_name || "未设置姓名"}

{profile?.email}

角色: {profile?.role === 'admin' ? '管理员' : '成员'}
注册时间: {formatDate(profile?.created_at)}
{/* Edit Form */}

基本信息

邮箱不可修改

setForm({ ...form, full_name: e.target.value })} placeholder="请输入姓名" className="cyber-input" />
setForm({ ...form, phone: e.target.value })} placeholder="请输入手机号" className="cyber-input" />

代码托管账号

setForm({ ...form, github_username: e.target.value })} placeholder="your-github-username" className="cyber-input" />
setForm({ ...form, gitlab_username: e.target.value })} placeholder="your-gitlab-username" className="cyber-input" />
{/* SSH Key Management */}

SSH 密钥管理

使用 SSH 密钥访问 Git 仓库

生成 SSH 密钥对后,将公钥添加到 GitHub/GitLab,即可使用 SSH URL 访问私有仓库。私钥将被加密存储。

{!sshKey.has_key ? (

尚未生成 SSH 密钥

) : (
{/* Public Key Display */}