import { useCallback, useEffect, useState } from 'react' import { Copy, KeyRound, LogIn, RefreshCw, ScrollText, UserCog, X } from 'lucide-react' import { useDialog } from '../components/Dialog' import api, { AuditLog, LoginLog } from '../services/api' import { copyToClipboard } from '../utils/clipboard' interface SubUserItem { id: string username: string container_names: string[] container_uuids: string[] container_name: string container_uuid: string access_code: string password?: string created_at: string last_login: string last_login_ip: string last_login_ua: string } interface AuditLogExt extends AuditLog { ip?: string user_agent?: string success?: boolean error?: string } export default function SubUserManagement() { const dialog = useDialog() const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const [auditLogs, setAuditLogs] = useState(null) const [loginLogs, setLoginLogs] = useState(null) const [modalTitle, setModalTitle] = useState('') const [passwordUser, setPasswordUser] = useState(null) const [rotatingPassword, setRotatingPassword] = useState(false) const [logPage, setLogPage] = useState(1) const [logPageSize, setLogPageSize] = useState(10) const fetchUsers = useCallback(async () => { try { const res = await api.get<{ success: boolean; data: SubUserItem[] }>('/sub-users') setUsers(res.data.data || []) } catch (err) { console.error(err) } finally { setLoading(false) } }, []) useEffect(() => { fetchUsers() }, [fetchUsers]) const managementUrl = (user: SubUserItem) => `${window.location.origin}/login?code=${user.access_code}` const copyText = async (text: string) => { await copyToClipboard(text) } const rotatePassword = async (user: SubUserItem) => { setRotatingPassword(true) try { const res = await api.post(`/sub-users/${user.id}/rotate-password`) const data = res.data.data const updatedUser = { ...user, username: data?.username || user.username, access_code: data?.access_code || user.access_code, password: data?.password || '', } setUsers((prev) => prev.map((item) => (item.id === user.id ? updatedUser : item))) setPasswordUser(updatedUser) } catch (err: unknown) { const error = err as { response?: { data?: { message?: string } } } dialog.alert('轮换失败', error.response?.data?.message || '请稍后重试') } finally { setRotatingPassword(false) } } const showAuditLogs = async (user: SubUserItem) => { try { const res = await api.get(`/sub-users/${user.id}/audit-logs`) setAuditLogs(res.data.data || []) setLoginLogs(null) setModalTitle(`${user.username} - 操作日志`) setLogPage(1) } catch { dialog.alert('错误', '获取操作日志失败') } } const showLoginLogs = async (user: SubUserItem) => { try { const res = await api.get(`/sub-users/${user.id}/login-logs`) setLoginLogs(res.data.data || []) setAuditLogs(null) setModalTitle(`${user.username} - 登录日志`) setLogPage(1) } catch { dialog.alert('错误', '获取登录日志失败') } } const closeModal = () => { setAuditLogs(null) setLoginLogs(null) } const currentLogTotal = auditLogs?.length ?? loginLogs?.length ?? 0 const logTotalPages = Math.max(1, Math.ceil(currentLogTotal / logPageSize)) const currentLogPage = Math.min(logPage, logTotalPages) const logStart = (currentLogPage - 1) * logPageSize const currentAuditLogs = auditLogs?.slice(logStart, logStart + logPageSize) const currentLoginLogs = loginLogs?.slice(logStart, logStart + logPageSize) if (loading) { return (
) } return (

子用户管理

容器分配的子用户列表,共 {users.length} 个

{users.length === 0 ? (
暂无子用户
) : ( {users.map((user, index) => ( ))}
# 容器名称 UUID 最后登录 操作
{index + 1} {user.container_name || '-'} {user.container_uuid || '-'} {user.last_login ? (
{user.last_login}
{user.last_login_ip}
) : ( 从未登录 )}
)}
{passwordUser && (

查看密码

用户 {passwordUser.username}
地址
{managementUrl(passwordUser)}
密码
{passwordUser.password || '未保存,请轮换生成新密码'} {passwordUser.password && ( )}
)} {/* Log Modal */} {(auditLogs || loginLogs) && (

{modalTitle}

{auditLogs && ( {auditLogs.length === 0 ? ( ) : currentAuditLogs?.map((log, i) => ( ))}
操作时间 操作 IP UA 结果
暂无操作日志
{log.time} {log.action} {log.ip || '-'} {log.user_agent || '-'} {log.success !== undefined ? ( log.success ? ( 成功 ) : ( {log.error ? '失败' : '失败'} ) ) : ( - )}
)} {loginLogs && ( {loginLogs.length === 0 ? ( ) : currentLoginLogs?.map((log, i) => ( ))}
登录时间 登录 IP UA 结果
暂无登录日志
{log.time} {log.ip} {log.user_agent} {log.success ? ( 成功 ) : ( 失败 )}
)}
{currentLogTotal > 0 && (
显示 {logStart + 1}-{Math.min(logStart + logPageSize, currentLogTotal)} / {currentLogTotal}
{currentLogPage} / {logTotalPages}
)}
)}
) }