feat: Agent CLI 与终端审计回放全面优化

- Agent CLI: 多轮对话、SSH 长连接、流式推理、Evolver 按用户隔离、历史侧栏
- 终端审计: 回放播放器、命令书签、筛选分页导出、命令输出统计、录制落盘修复
- 修复 session_commands 表迁移、Kill 竞态导致录制丢失、无录制会话回放提示
- 审计历史页只读(移除删除),移除回放弹框录制搜索
This commit is contained in:
Logan committed 2026-07-01 12:13:53 +08:00
1 parent 9a3dd304f7
commit 1acb0c49af
81 files changed
+3916 -1655

No files matched your search

@@ -118,29 +118,29 @@ export function SystemUserTab({
{/* Filters */}
<div className="flex items-center justify-between">
<div className="relative w-64">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-500" />
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--dg-text-muted)]" />
<Input
placeholder="搜索用户..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-9 bg-slate-800/50 border-slate-700 text-slate-200 placeholder:text-slate-500"
className="pl-9 h-9 bg-[var(--dg-bg-tertiary)]/50 border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
/>
</div>
<div className="flex items-center gap-2">
{hasPermission('system:user') && (
<Button variant="outline" className="border-slate-700 text-slate-300 hover:text-slate-100 hover:bg-slate-800" onClick={() => setImportDialogOpen('csv')}>
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]" onClick={() => setImportDialogOpen('csv')}>
<Upload className="h-4 w-4 mr-2" />
批量导入
</Button>
)}
{hasPermission('system:user') && (
<Button variant="outline" className="border-slate-700 text-slate-300 hover:text-slate-100 hover:bg-slate-800" onClick={() => setImportDialogOpen('ldap')}>
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]" onClick={() => setImportDialogOpen('ldap')}>
<Network className="h-4 w-4 mr-2" />
LDAP 导入
</Button>
)}
{hasPermission('system:user') && (
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={openCreate}>
<Button className="bg-[var(--dg-blue)] hover:opacity-90 text-white" onClick={openCreate}>
<Plus className="h-4 w-4 mr-2" />
新建用户
</Button>
@@ -149,58 +149,58 @@ export function SystemUserTab({
</div>
{/* Users Table */}
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-0">
<div className="border-t border-slate-800">
<div className="border-t border-[var(--dg-border)]">
<Table>
<TableHeader>
<TableRow className="border-slate-800 hover:bg-transparent">
<TableHead className="text-slate-400">用户信息</TableHead>
<TableHead className="text-slate-400">联系方式</TableHead>
<TableHead className="text-slate-400">角色</TableHead>
<TableHead className="text-slate-400">来源</TableHead>
<TableHead className="text-slate-400">状态</TableHead>
<TableHead className="text-slate-400">最后登录</TableHead>
<TableHead className="text-slate-400 text-right">操作</TableHead>
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
<TableHead className="text-[var(--dg-text-secondary)]">用户信息</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">联系方式</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">角色</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">来源</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">状态</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">最后登录</TableHead>
<TableHead className="text-[var(--dg-text-secondary)] text-right">操作</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow className="border-slate-800">
<TableRow className="border-[var(--dg-border)]">
<TableCell colSpan={7} className="text-center py-12">
<div className="flex items-center justify-center">
<div className="h-6 w-6 border-2 border-slate-700 border-t-blue-500 rounded-full animate-spin" />
<div className="h-6 w-6 border-2 border-[var(--dg-border)] border-t-[var(--dg-blue)] rounded-full animate-spin" />
</div>
</TableCell>
</TableRow>
) : filteredUsers.length === 0 ? (
<TableRow className="border-slate-800">
<TableCell colSpan={7} className="text-center py-12 text-sm text-slate-500">
<TableRow className="border-[var(--dg-border)]">
<TableCell colSpan={7} className="text-center py-12 text-sm text-[var(--dg-text-muted)]">
暂无用户数据
</TableCell>
</TableRow>
) : (
filteredUsers.map((user) => (
<TableRow key={user.id} className="border-slate-800 hover:bg-slate-800/50">
<TableRow key={user.id} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]/50">
<TableCell>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm font-medium">
<div className="w-8 h-8 rounded-full bg-[var(--dg-blue)] flex items-center justify-center text-white text-sm font-medium">
{(user.nickname || user.username || '?').charAt(0)}
</div>
<div>
<p className="text-sm font-medium text-slate-200">{user.nickname || user.username}</p>
<p className="text-xs text-slate-500">@{user.username}</p>
<p className="text-sm font-medium text-[var(--dg-text-primary)]">{user.nickname || user.username}</p>
<p className="text-xs text-[var(--dg-text-muted)]">@{user.username}</p>
</div>
</div>
</TableCell>
<TableCell>
<div className="space-y-1">
<div className="flex items-center gap-1 text-sm text-slate-400">
<div className="flex items-center gap-1 text-sm text-[var(--dg-text-secondary)]">
<Mail className="h-3.5 w-3.5" />
{user.email || '--'}
</div>
{user.phone && (
<div className="flex items-center gap-1 text-sm text-slate-400">
<div className="flex items-center gap-1 text-sm text-[var(--dg-text-secondary)]">
<Phone className="h-3.5 w-3.5" />
{user.phone}
</div>
@@ -210,7 +210,7 @@ export function SystemUserTab({
<TableCell>
<div className="flex flex-wrap gap-1">
{(user.roles || []).map((role: string) => (
<Badge key={role} variant="outline" className="border-slate-700 text-slate-400">
<Badge key={role} variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
{role}
</Badge>
))}
@@ -218,18 +218,18 @@ export function SystemUserTab({
</TableCell>
<TableCell>
{user.authSource === 'ldap' ? (
<Badge variant="outline" className="border-cyan-500/30 text-cyan-400 bg-cyan-500/10">
<Badge variant="outline" className="border-[var(--dg-cyan)]/30 text-[var(--dg-cyan)] bg-[var(--dg-cyan)]/10">
LDAP
</Badge>
) : (
<Badge variant="outline" className="border-slate-600/30 text-slate-400 bg-slate-600/10">
<Badge variant="outline" className="border-[var(--dg-text-muted)]/30 text-[var(--dg-text-muted)] bg-[var(--dg-text-muted)]/10">
本地
</Badge>
)}
</TableCell>
<TableCell>{getUserStatusBadge(user.status)}</TableCell>
<TableCell>
<span className="text-sm text-slate-400">
<span className="text-sm text-[var(--dg-text-secondary)]">
{user.lastLoginAt ? new Date(user.lastLoginAt).toLocaleString() : '--'}
</span>
</TableCell>
@@ -238,7 +238,7 @@ export function SystemUserTab({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-slate-400 hover:text-slate-100"
className="h-8 w-8 text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
onClick={() => setSelectedUser(user)}
>
<Eye className="h-4 w-4" />
@@ -247,7 +247,7 @@ export function SystemUserTab({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-slate-400 hover:text-slate-100"
className="h-8 w-8 text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
onClick={() => openEdit(user)}
>
<Edit className="h-4 w-4" />
@@ -257,7 +257,7 @@ export function SystemUserTab({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-slate-400 hover:text-red-400"
className="h-8 w-8 text-[var(--dg-text-secondary)] hover:text-[var(--dg-red)]"
onClick={() => handleDelete(user)}
>
<Trash2 className="h-4 w-4" />