import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Plus, RefreshCw, Trash2, Power, LogIn, Upload, Download, CheckCircle2, XCircle, Search, MoreVertical, AlertCircle } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import { cn } from '../lib/utils'; export default function Tokens() { const { token: adminToken } = useAuth(); const [tokens, setTokens] = useState([]); const [isLoading, setIsLoading] = useState(true); const [selectedTokens, setSelectedTokens] = useState(new Set()); const [manualUrl, setManualUrl] = useState(''); const [isAdding, setIsAdding] = useState(false); const [message, setMessage] = useState({ type: '', content: '' }); const fetchTokens = async () => { setIsLoading(true); try { const res = await fetch('/admin/tokens', { headers: { 'X-Admin-Token': adminToken } }); const data = await res.json(); // Fetch details for names if (data.length > 0) { const indices = data.map(t => t.index); const detailsRes = await fetch('/admin/tokens/details', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken }, body: JSON.stringify({ indices }) }); const details = await detailsRes.json(); const detailsMap = {}; details.forEach(d => detailsMap[d.index] = d); const enrichedTokens = data.map(t => ({ ...t, ...detailsMap[t.index] })); setTokens(enrichedTokens); } else { setTokens([]); } } catch (error) { console.error('Failed to fetch tokens', error); setMessage({ type: 'error', content: '加载 Token 失败' }); } finally { setIsLoading(false); } }; useEffect(() => { fetchTokens(); }, [adminToken]); const handleGoogleLogin = async () => { try { const res = await fetch('/admin/tokens/login', { method: 'POST', headers: { 'X-Admin-Token': adminToken } }); const data = await res.json(); if (data.success && data.authUrl) { window.open(data.authUrl, '_blank'); setMessage({ type: 'info', content: '已打开登录页面,完成后请刷新列表' }); // Auto refresh after 10s setTimeout(fetchTokens, 10000); } else { setMessage({ type: 'error', content: data.message || '启动登录失败' }); } } catch (error) { setMessage({ type: 'error', content: '请求失败: ' + error.message }); } }; const handleManualAdd = async () => { if (!manualUrl) return; setIsAdding(true); try { // Extract code from URL if full URL is pasted let code = manualUrl; if (manualUrl.includes('code=')) { code = new URL(manualUrl).searchParams.get('code'); } const res = await fetch('/admin/tokens/callback', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken }, body: JSON.stringify({ callbackUrl: manualUrl }) }); const data = await res.json(); if (data.success) { setMessage({ type: 'success', content: 'Token 添加成功' }); setManualUrl(''); fetchTokens(); } else { setMessage({ type: 'error', content: data.error || '添加失败' }); } } catch (error) { setMessage({ type: 'error', content: '请求失败: ' + error.message }); } finally { setIsAdding(false); } }; const toggleToken = async (index, enable) => { try { const res = await fetch('/admin/tokens/toggle', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken }, body: JSON.stringify({ index, enable }) }); if (res.ok) { fetchTokens(); } } catch (error) { console.error('Toggle failed', error); } }; const deleteToken = async (index) => { if (!confirm('确定要删除这个 Token 吗?')) return; try { const res = await fetch(`/admin/tokens/${index}`, { method: 'DELETE', headers: { 'X-Admin-Token': adminToken } }); if (res.ok) { fetchTokens(); const newSelected = new Set(selectedTokens); newSelected.delete(index); setSelectedTokens(newSelected); } } catch (error) { console.error('Delete failed', error); } }; const toggleSelection = (index) => { const newSelected = new Set(selectedTokens); if (newSelected.has(index)) { newSelected.delete(index); } else { newSelected.add(index); } setSelectedTokens(newSelected); }; const selectAll = () => { if (selectedTokens.size === tokens.length) { setSelectedTokens(new Set()); } else { setSelectedTokens(new Set(tokens.map(t => t.index))); } }; const exportTokens = async () => { if (selectedTokens.size === 0) return alert('请先选择要导出的账号'); try { const res = await fetch('/admin/tokens/export', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Admin-Token': adminToken }, body: JSON.stringify({ indices: Array.from(selectedTokens) }) }); if (res.ok) { const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `tokens_export_${new Date().toISOString().slice(0, 10)}.zip`; a.click(); } } catch (error) { console.error('Export failed', error); } }; const importTokens = () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.zip'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); try { const res = await fetch('/admin/tokens/import', { method: 'POST', headers: { 'X-Admin-Token': adminToken }, body: formData }); const data = await res.json(); if (data.success) { setMessage({ type: 'success', content: `成功导入 ${data.count} 个 Token` }); fetchTokens(); } else { setMessage({ type: 'error', content: data.error || '导入失败' }); } } catch (error) { setMessage({ type: 'error', content: '导入失败: ' + error.message }); } }; input.click(); }; return (
管理 Google OAuth 账号和 Access Token
{t.email || 'No Email'}