// 终端审计 — 在线会话子面板 // Logan.Li import { useState, useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; import { cn } from '@/lib/utils'; import { useTranslation } from 'react-i18next'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Search, RefreshCw, XOctagon, Eye, Activity, Timer, TimerOff } from 'lucide-react'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { LiveMonitorDialog } from '@/components/system/LiveMonitorDialog'; import { RdpAuditViewer } from '@/components/system/RdpAuditViewer'; import { useTerminalSessions, useTerminalSessionMutations } from '@/hooks/api'; import { rdpSessionApi } from '@/api'; import { unwrapList } from '@/hooks/api/unwrap'; import type { TerminalSession } from './types'; import { formatDuration, getStatusBadge } from './helpers'; const REFRESH_SEC = 10; interface ActiveSessionsPanelProps { userToggledOff: boolean; onUserToggledOff: () => void; } function useElapsedTimer(startTime: string | undefined): number { const [elapsed, setElapsed] = useState(0); useEffect(() => { if (!startTime) return; const start = new Date(startTime).getTime(); const tick = () => setElapsed(Math.floor((Date.now() - start) / 1000)); tick(); const id = setInterval(tick, 1000); return () => clearInterval(id); }, [startTime]); return elapsed; } function ActiveSessionRow({ session, onKill, onMonitor, }: { session: TerminalSession; onKill: (id: number, username: string) => void; onMonitor: (id: number) => void; }) { const { t } = useTranslation(); const elapsed = useElapsedTimer(session.start_time); return ( {session.id} {session.username || '--'} {session.server_name || '--'} {session.server_ip} {session.server_port} {session.start_time ? new Date(session.start_time).toLocaleString() : '--'} {formatDuration(elapsed)} {getStatusBadge(session.status)} onKill(session.id, session.username)} title={t('audit.terminal.killTitle')} > onMonitor(session.id)} title={t('audit.terminal.liveMonitor')} > ); } function RdpActiveSessionRow({ session, onKill, onMonitor, }: { session: TerminalSession; onKill: (id: number, username: string) => void; onMonitor: (id: number) => void; }) { const { t } = useTranslation(); const elapsed = useElapsedTimer(session.start_time); return ( {session.id} {session.username || '--'} {session.server_name || '--'} {session.server_ip} {session.start_time ? new Date(session.start_time).toLocaleString() : '--'} {formatDuration(elapsed)} onKill(session.id, session.username)} title={t('audit.terminal.killTitle')}> onMonitor(session.id)} title="RDP 实时审计"> ); } export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: ActiveSessionsPanelProps) { const { t } = useTranslation(); const confirm = useConfirm(); const { killMutation } = useTerminalSessionMutations(); const { data, isLoading, isFetching, refetch } = useTerminalSessions({ page: 1, page_size: 100, status: 'active', }); const rdpQuery = useQuery({ queryKey: ['rdp-sessions', 'active'], queryFn: async () => { const resp = await rdpSessionApi.list({ page: 1, page_size: 100, status: 'active' }); return unwrapList(resp); }, }); const sessions = data?.list ?? []; const rdpSessions = rdpQuery.data ?? []; const [keyword, setKeyword] = useState(''); const [autoRefresh, setAutoRefresh] = useState(() => !userToggledOff); const [monitorOpen, setMonitorOpen] = useState(false); const [monitorSessionId, setMonitorSessionId] = useState(null); const [rdpOpen, setRdpOpen] = useState(false); const [rdpSessionId, setRdpSessionId] = useState(null); // 用户未手动关闭时,回到在线会话自动开启刷新 useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect if (!userToggledOff) setAutoRefresh(true); }, [userToggledOff]); useEffect(() => { if (!autoRefresh) return; const id = setInterval(() => { refetch(); rdpQuery.refetch(); }, REFRESH_SEC * 1000); return () => clearInterval(id); }, [autoRefresh, refetch, rdpQuery]); const filtered = sessions.filter((s) => { const kw = keyword.toLowerCase(); return ( !kw || (s.username || '').toLowerCase().includes(kw) || (s.server_name || '').toLowerCase().includes(kw) || (s.server_ip || '').toLowerCase().includes(kw) ); }); const handleKill = async (id: number, username: string) => { const ok = await confirm({ title: t('audit.terminal.killTitle'), description: t('audit.terminal.killConfirm', { user: username }), confirmText: t('audit.terminal.killConfirmBtn'), variant: 'destructive', }); if (!ok) return; try { await killMutation.mutateAsync(id); } catch { /* noop */ } }; const openMonitor = (sessionId: number) => { setMonitorSessionId(sessionId); setMonitorOpen(true); }; const openRdpMonitor = (sessionId: number) => { setRdpSessionId(sessionId); setRdpOpen(true); }; const killRdp = async (id: number, username: string) => { const ok = await confirm({ title: t('audit.terminal.killTitle'), description: t('audit.terminal.killConfirm', { user: username }), confirmText: t('audit.terminal.killConfirmBtn'), variant: 'destructive', }); if (!ok) return; await rdpSessionApi.kill(id).catch(() => undefined); rdpQuery.refetch(); }; return ( <> setKeyword(e.target.value)} className="pl-9 h-9 dg-input" /> refetch()} disabled={isFetching} > {t('common.refresh')} { if (autoRefresh) onUserToggledOff(); setAutoRefresh(!autoRefresh); }} title={autoRefresh ? t('audit.terminal.autoRefreshHintOn', { sec: REFRESH_SEC }) : t('audit.terminal.autoRefreshHintOff')} > {autoRefresh ? ( <> {REFRESH_SEC}s > ) : ( <> {t('audit.terminal.autoRefresh')} > )} {isLoading ? ( ) : filtered.length === 0 ? ( {t('audit.terminal.noActive')} ) : ( {t('audit.terminal.col.id')} {t('audit.terminal.col.user')} {t('audit.terminal.col.targetServer')} {t('audit.terminal.col.port')} {t('audit.terminal.col.startTime')} {t('audit.terminal.col.duration')} {t('audit.terminal.col.status')} {t('audit.terminal.col.actions')} {filtered.map((s) => ( ))} )} RDP 在线会话 {rdpQuery.isLoading ? ( ) : rdpSessions.length === 0 ? ( 暂无 RDP 在线会话 ) : ( {t('audit.terminal.col.id')} {t('audit.terminal.col.user')} {t('audit.terminal.col.targetServer')} {t('audit.terminal.col.startTime')} {t('audit.terminal.col.duration')} {t('audit.terminal.col.actions')} {rdpSessions.map((s) => ( ))} )} > ); }
{t('audit.terminal.noActive')}
暂无 RDP 在线会话