'use client'; import * as React from 'react'; import Link from 'next/link'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { motion } from 'motion/react'; import { AlertTriangle, Check, Copy, Info, Key, Loader2, Plus, RefreshCw, Shield, Trash2, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Switch } from '@/components/ui/switch'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; import type { CreateTokenResponse } from '@/lib/services/user'; import { UserService } from '@/lib/services/user'; import { useAuth } from '@/components/providers/auth-provider'; import { toast } from 'sonner'; export function AccessTokenMain() { const { user } = useAuth(); const queryClient = useQueryClient(); const [createDialogOpen, setCreateDialogOpen] = React.useState(false); const [viewDialogOpen, setViewDialogOpen] = React.useState(false); const [tokenName, setTokenName] = React.useState(''); const [tokenIsAdmin, setTokenIsAdmin] = React.useState(false); const [copiedId, setCopiedId] = React.useState(null); const [newCreatedToken, setNewCreatedToken] = React.useState(null); // 获取 Token 列表 const accessTokensQuery = useQuery({ queryKey: ['user', 'access-tokens'], queryFn: () => UserService.getAccessTokens(), }); // 创建 Token const createTokenMutation = useMutation({ mutationFn: ({ name, isAdmin }: { name: string; isAdmin: boolean }) => UserService.createAccessToken(name, isAdmin), onSuccess: (data) => { setNewCreatedToken(data); setTokenName(''); setTokenIsAdmin(false); setCreateDialogOpen(false); setViewDialogOpen(true); void queryClient.invalidateQueries({ queryKey: ['user', 'access-tokens'], }); toast.success('访问令牌创建成功'); }, onError: (error: Error) => { toast.error(error.message || '创建访问令牌失败'); }, }); // 删除 Token const deleteTokenMutation = useMutation({ mutationFn: (id: number) => UserService.deleteAccessToken(id), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ['user', 'access-tokens'], }); toast.success('访问令牌已撤销'); }, onError: (error: Error) => { toast.error(error.message || '删除访问令牌失败'); }, }); // 轮换 Token const rotateTokenMutation = useMutation({ mutationFn: (id: number) => UserService.rotateAccessToken(id), onSuccess: (data) => { setNewCreatedToken(data); setViewDialogOpen(true); void queryClient.invalidateQueries({ queryKey: ['user', 'access-tokens'], }); toast.success('访问令牌轮换成功,旧密钥已失效'); }, onError: (error: Error) => { toast.error(error.message || '轮换访问令牌失败'); }, }); const handleCreateToken = (e: React.FormEvent) => { e.preventDefault(); if (!tokenName.trim()) { toast.error('请输入令牌名称'); return; } createTokenMutation.mutate({ name: tokenName.trim(), isAdmin: tokenIsAdmin, }); }; const handleDeleteToken = (id: number, name: string) => { if ( window.confirm( `确定要删除并撤销令牌「${name}」吗?删除后此令牌将立即失效且不可恢复。`, ) ) { deleteTokenMutation.mutate(id); } }; const handleRotateToken = (id: number, name: string) => { if ( window.confirm( `确定要轮换令牌「${name}」的密钥吗?轮换后系统将生成全新密钥,原令牌密钥将立即失效。`, ) ) { rotateTokenMutation.mutate(id); } }; const handleCopyText = async (text: string, id: number) => { try { await navigator.clipboard.writeText(text); setCopiedId(id); toast.success('复制成功'); setTimeout(() => setCopiedId(null), 2000); } catch { toast.error('复制失败'); } }; const formatDate = (dateStr?: string) => { if (!dateStr) return '未使用'; return new Date(dateStr).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); }; return (
设置 访问令牌

个人访问令牌 (AccessToken)

管理您的 API 访问密钥,用于开发或第三方工具直接调用系统 API

{/* 安全警告提示 */}
安全提示:

访问令牌具有您账户的完整接口调用权限。为了您的账户与资产安全,请切勿通过任何代码库提交、即时通讯工具或公共媒介泄露此令牌。推荐按需创建,不使用时及时撤销。

活动令牌列表 当前可用的所有访问令牌 {accessTokensQuery.isPending ? (
) : (accessTokensQuery.data ?? []).length > 0 ? (
{(accessTokensQuery.data ?? []).map((token) => (
{token.name} {token.is_admin ? ( 管理员 ) : ( 用户令牌 )}
{token.masked_token}
创建于: {formatDate(token.created_at)}
))}
) : (
您当前暂无生成任何访问令牌
)}
{/* 创建令牌 Dialog */}
生成新令牌 请为新的访问令牌设置一个易于识别的名称,以便将来管理。
setTokenName(e.target.value)} disabled={createTokenMutation.isPending} className='rounded-xl border border-dashed focus:border-primary focus:ring-0 focus-visible:ring-0' />
{user?.is_admin && (

开启后此令牌可访问 /admin/** 管理端点,默认关闭

)}
{/* 明文 Token 显示 Dialog (仅显示一次) */} { if (!open) { setNewCreatedToken(null); setViewDialogOpen(false); } }} > 令牌密钥已就绪 {newCreatedToken && (
{/* 明文 Token 文本框 */}
{newCreatedToken.token}
{/* 强提示 */}
重要提示:

这是您唯一一次能够查看此访问令牌明文密钥的机会。请立即将其复制并安全地保存。

)}
); }