import { useState } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Search, RefreshCw, Trash2, Play } from 'lucide-react'; import { useTerminalSessions, useTerminalSessionMutations } from '@/hooks/api/useTerminalSessions'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { ReplayDialog } from './ReplayDialog'; function formatDuration(sec: number): string { if (!sec || sec < 0) return '--'; if (sec < 60) return `${sec}秒`; const m = Math.floor(sec / 60); const s = sec % 60; if (m < 60) return `${m}分${s > 0 ? `${s}秒` : ''}`; const h = Math.floor(m / 60); const rm = m % 60; return `${h}时${rm > 0 ? `${rm}分` : ''}`; } function formatSize(bytes: number): string { if (!bytes) return '0 B'; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(2)} MB`; } /** * 终端审计 Tab — WebSSH 会话录制列表与回放(JumpServer 式审计) // Logan.Li */ export function TerminalSessionsTab() { const confirm = useConfirm(); const { data, isLoading: loading, refetch } = useTerminalSessions({ page: 1, page_size: 100 }); const sessions = data?.list ?? []; const { deleteMutation, batchDeleteMutation } = useTerminalSessionMutations(); const [keyword, setKeyword] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [selected, setSelected] = useState>(new Set()); const [replayId, setReplayId] = useState(null); const [replayOpen, setReplayOpen] = useState(false); const filtered = sessions.filter((s) => { const kw = keyword.toLowerCase(); const kwMatch = !kw || (s.username || '').toLowerCase().includes(kw) || (s.server_name || '').toLowerCase().includes(kw) || (s.server_ip || '').toLowerCase().includes(kw) || (s.client_ip || '').toLowerCase().includes(kw); const stMatch = !statusFilter || s.status === statusFilter; return kwMatch && stMatch; }); const getStatusBadge = (status: string) => { if (status === 'active') { return ( 活动中 ); } if (status === 'failed') { return ( 失败 ); } return ( 已关闭 ); }; const toggleSelect = (id: number) => { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const toggleAll = () => { setSelected((prev) => prev.size === filtered.length ? new Set() : new Set(filtered.map((s) => s.id)) ); }; const openReplay = (id: number) => { setReplayId(id); setReplayOpen(true); }; const handleDelete = async (id: number) => { const ok = await confirm({ title: '删除会话记录', description: '确认删除该终端会话记录?删除后无法恢复。', confirmText: '删除', variant: 'destructive', }); if (!ok) return; try { await deleteMutation.mutateAsync(id); setSelected((prev) => { const next = new Set(prev); next.delete(id); return next; }); } catch { /* noop */ } }; const handleBatchDelete = async () => { if (selected.size === 0) return; const ok = await confirm({ title: '批量删除', description: `确认删除选中的 ${selected.size} 条终端会话记录?`, confirmText: '删除', variant: 'destructive', }); if (!ok) return; try { await batchDeleteMutation.mutateAsync(Array.from(selected)); setSelected(new Set()); } catch { /* noop */ } }; const allChecked = filtered.length > 0 && selected.size === filtered.length; return ( <> {/* Filters */}
setKeyword(e.target.value)} className="pl-9 h-9 bg-slate-800/50 border-slate-700 text-slate-200 placeholder:text-slate-500" />
{['', 'active', 'closed', 'failed'].map((st) => ( ))}
{selected.size > 0 && ( )}
{/* Sessions Table */}
{loading ? (
) : filtered.length === 0 ? (

暂无终端会话记录

) : ( 用户 服务器 来源 IP 状态 时长 录制 开始时间 操作 {filtered.map((s) => ( toggleSelect(s.id)} className="accent-blue-600 cursor-pointer" onClick={(e) => e.stopPropagation()} /> {s.username || '--'}
{s.server_name || '--'} {s.server_ip}:{s.server_port}
{s.client_ip || '--'} {getStatusBadge(s.status)} {formatDuration(s.duration)} {formatSize(s.record_size)} {s.start_time ? new Date(s.start_time).toLocaleString() : '--'}
))}
)}
); }