import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Plus, Trash2, RefreshCw, Copy, Activity, User, Settings, Shield, Clock, Users } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { request as invoke } from '../utils/request'; import { showToast } from '../components/common/ToastContainer'; import { copyToClipboard } from '../utils/clipboard'; interface UserToken { id: string; token: string; username: string; description?: string; enabled: boolean; expires_type: string; expires_at?: number; max_ips: number; curfew_start?: string; curfew_end?: string; created_at: number; updated_at: number; last_used_at?: number; total_requests: number; total_tokens_used: number; } interface UserTokenStats { total_tokens: number; active_tokens: number; total_users: number; today_requests: number; } // interface CreateTokenRequest omitted as it's not explicitly used for typing variables const UserToken: React.FC = () => { const { t } = useTranslation(); const [tokens, setTokens] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false); const [creating, setCreating] = useState(false); // Edit State const [showEditModal, setShowEditModal] = useState(false); const [editingToken, setEditingToken] = useState(null); const [editUsername, setEditUsername] = useState(''); const [editDesc, setEditDesc] = useState(''); const [editMaxIps, setEditMaxIps] = useState(0); const [editCurfewStart, setEditCurfewStart] = useState(''); const [editCurfewEnd, setEditCurfewEnd] = useState(''); const [updating, setUpdating] = useState(false); // Create Form State const [newUsername, setNewUsername] = useState(''); const [newDesc, setNewDesc] = useState(''); const [newExpiresType, setNewExpiresType] = useState('month'); // day, week, month, never, custom const [newMaxIps, setNewMaxIps] = useState(0); const [newCurfewStart, setNewCurfewStart] = useState(''); const [newCurfewEnd, setNewCurfewEnd] = useState(''); const [newCustomExpires, setNewCustomExpires] = useState(''); // datetime-local value const loadData = async () => { setLoading(true); try { const [tokensData, statsData] = await Promise.all([ invoke('list_user_tokens'), invoke('get_user_token_summary') ]); setTokens(tokensData); setStats(statsData); } catch (e) { console.error('Failed to load user tokens', e); showToast(t('common.load_failed') || 'Failed to load data', 'error'); } finally { setLoading(false); } }; useEffect(() => { loadData(); }, []); const handleCreate = async () => { if (!newUsername) { showToast(t('user_token.username_required') || 'Username is required', 'error'); return; } // 验证自定义时间 if (newExpiresType === 'custom' && !newCustomExpires) { showToast(t('user_token.custom_expires_required') || 'Please select a custom expiration time', 'error'); return; } setCreating(true); try { // 计算自定义过期时间戳 const customExpiresAt = newExpiresType === 'custom' && newCustomExpires ? Math.floor(new Date(newCustomExpires).getTime() / 1000) : undefined; await invoke('create_user_token', { request: { username: newUsername, expires_type: newExpiresType, description: newDesc || null, max_ips: newMaxIps, curfew_start: newCurfewStart || null, curfew_end: newCurfewEnd || null, custom_expires_at: customExpiresAt || null } }); showToast(t('common.create_success') || 'Created successfully', 'success'); setShowCreateModal(false); setNewUsername(''); setNewDesc(''); setNewExpiresType('month'); setNewMaxIps(0); setNewCurfewStart(''); setNewCurfewEnd(''); setNewCustomExpires(''); loadData(); } catch (e) { console.error('Failed to create token', e); showToast(String(e), 'error'); } finally { setCreating(false); } }; const handleDelete = async (id: string) => { try { await invoke('delete_user_token', { id }); showToast(t('common.delete_success') || 'Deleted successfully', 'success'); loadData(); } catch (e) { showToast(String(e), 'error'); } }; const handleEdit = (token: UserToken) => { console.log('Editing token:', token); // 调试日志 setEditingToken(token); setEditUsername(token.username); setEditDesc(token.description || ''); setEditMaxIps(token.max_ips ?? 0); // 使用 ?? 确保 null/undefined 变为 0 setEditCurfewStart(token.curfew_start ?? ''); setEditCurfewEnd(token.curfew_end ?? ''); setShowEditModal(true); }; const handleUpdate = async () => { if (!editingToken) return; if (!editUsername) { showToast(t('user_token.username_required') || 'Username is required', 'error'); return; } setUpdating(true); try { await invoke('update_user_token', { id: editingToken.id, request: { username: editUsername, description: editDesc || undefined, max_ips: editMaxIps, // 使用双层包装: undefined = 不更新, null = 清空, string = 设置值 curfew_start: editCurfewStart === '' ? null : editCurfewStart, curfew_end: editCurfewEnd === '' ? null : editCurfewEnd } }); showToast(t('common.update_success') || 'Updated successfully', 'success'); setShowEditModal(false); setEditingToken(null); loadData(); } catch (e) { console.error('Failed to update token', e); showToast(String(e), 'error'); } finally { setUpdating(false); } }; const handleRenew = async (id: string, type: string) => { try { await invoke('renew_user_token', { id, expiresType: type }); showToast(t('user_token.renew_success') || 'Renewed successfully', 'success'); loadData(); } catch (e) { showToast(String(e), 'error'); } }; const handleCopyToken = async (text: string) => { const success = await copyToClipboard(text); if (success) { showToast(t('common.copied') || 'Copied to clipboard', 'success'); } else { showToast(t('common.copy_failed') || 'Failed to copy to clipboard', 'error'); } }; const formatTime = (ts?: number) => { if (!ts) return '-'; return new Date(ts * 1000).toLocaleString(); }; const getExpiresLabel = (type: string) => { switch (type) { case 'day': return t('user_token.expires_day', { defaultValue: '1 Day' }); case 'week': return t('user_token.expires_week', { defaultValue: '1 Week' }); case 'month': return t('user_token.expires_month', { defaultValue: '1 Month' }); case 'never': return t('user_token.expires_never', { defaultValue: 'Never' }); case 'custom': return t('user_token.expires_custom', { defaultValue: 'Custom' }); default: return type; } }; // Calculate expiration status style const getExpiresStatus = (expiresAt?: number) => { if (!expiresAt) return 'text-green-500'; const now = Date.now() / 1000; if (expiresAt < now) return 'text-red-500 font-bold'; if (expiresAt - now < 86400 * 3) return 'text-orange-500'; // Less than 3 days return 'text-green-500'; }; return ( {/* Header */}

{t('user_token.title', { defaultValue: 'User Tokens' })}

{/* Stats Cards Row */}
{stats?.total_users || 0}
{t('user_token.total_users', { defaultValue: 'Total Users' })}
{stats?.active_tokens || 0}
{t('user_token.active_tokens', { defaultValue: 'Active Tokens' })}
{stats?.total_tokens || 0}
{t('user_token.total_created', { defaultValue: 'Total Tokens' })}
{stats?.today_requests || 0}
{t('user_token.today_requests', { defaultValue: 'Today Requests' })}
{/* Token List */}
{tokens.map((token, index) => ( ))} {tokens.length === 0 && !loading && ( )}
{t('user_token.username', { defaultValue: 'Username' })} {t('user_token.token', { defaultValue: 'Token' })} {t('user_token.expires', { defaultValue: 'Expires' })} {t('user_token.usage', { defaultValue: 'Usage' })} {t('user_token.ip_limit', { defaultValue: 'IP Limit' })} {t('user_token.created', { defaultValue: 'Created' })} {t('common.actions', { defaultValue: 'Actions' })}
{token.username.substring(0, 2).toUpperCase()}
{token.username}
{token.description || '-'}
{token.token.substring(0, 8)}••••••••
{token.expires_at ? formatTime(token.expires_at) : t('user_token.never', { defaultValue: 'Never' })}
{getExpiresLabel(token.expires_type)} {token.expires_at && token.expires_at < Date.now() / 1000 && ( )}
{token.total_requests} reqs
{(token.total_tokens_used / 1000).toFixed(1)}k tokens
{token.max_ips === 0 ? {t('user_token.unlimited', { defaultValue: 'Unlimited' })} : {token.max_ips} IPs } {token.curfew_start && token.curfew_end && (
{token.curfew_start} - {token.curfew_end}
)}
{formatTime(token.created_at)}

{t('user_token.no_data', { defaultValue: 'No tokens found' })}

{/* Create Modal */} {showCreateModal && (

{t('user_token.create_title', { defaultValue: 'Create New Token' })}

setNewUsername(e.target.value)} placeholder={t('user_token.placeholder_username', { defaultValue: 'e.g. user1' })} />
setNewDesc(e.target.value)} placeholder={t('user_token.placeholder_desc', { defaultValue: 'Optional notes' })} />
setNewMaxIps(parseInt(e.target.value) || 0)} min="0" placeholder={t('user_token.placeholder_max_ips', { defaultValue: '0 = Unlimited' })} />
{/* Custom Expiration Time Picker */} {newExpiresType === 'custom' && (
setNewCustomExpires(e.target.value)} min={new Date().toISOString().slice(0, 16)} />
)}
setNewCurfewStart(e.target.value)} /> to setNewCurfewEnd(e.target.value)} />
)} {/* Edit Modal */} {showEditModal && editingToken && (

{t('user_token.edit_title', { defaultValue: 'Edit Token' })}

setEditUsername(e.target.value)} placeholder={t('user_token.placeholder_username', { defaultValue: 'e.g. user1' })} />
setEditDesc(e.target.value)} placeholder={t('user_token.placeholder_desc', { defaultValue: 'Optional notes' })} />
setEditMaxIps(parseInt(e.target.value) || 0)} min="0" placeholder={t('user_token.placeholder_max_ips', { defaultValue: '0 = Unlimited' })} />
setEditCurfewStart(e.target.value)} /> to setEditCurfewEnd(e.target.value)} />
)}
); }; export default UserToken;