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

@@ -74,30 +74,30 @@ export default function ClusterOverviewPage() {
useEffect(() => { loadData(); }, [loadData]);
const getNodeRole = (node: JsonData) => {
if (node?.role === 'master' || node?.role === 'control-plane') return 'bg-blue-500/20 text-blue-400 border-blue-500/30';
return 'bg-slate-500/20 text-slate-400 border-slate-500/30';
if (node?.role === 'master' || node?.role === 'control-plane') return 'bg-[var(--dg-blue)]/20 text-[var(--dg-blue)] border-[var(--dg-blue)]/30';
return 'bg-[var(--dg-text-muted)]/20 text-[var(--dg-text-secondary)] border-[var(--dg-text-muted)]/30';
};
return (
<div className="space-y-4">
{/* Breadcrumb */}
<div className="flex items-center gap-2 text-sm text-slate-500">
<Link to="/kubernetes" className="hover:text-slate-300 transition-colors">集群</Link>
<div className="flex items-center gap-2 text-sm text-[var(--dg-text-muted)]">
<Link to="/kubernetes" className="hover:text-[var(--dg-text-secondary)] transition-colors">集群</Link>
<ChevronRight className="h-3 w-3" />
<span className="text-slate-300">{clusterCode}</span>
<span className="text-[var(--dg-text-secondary)]">{clusterCode}</span>
</div>
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-slate-100">{clusterCode}</h1>
<p className="text-sm text-slate-500 mt-1">
<h1 className="text-2xl font-bold text-[var(--dg-text-primary)]">{clusterCode}</h1>
<p className="text-sm text-[var(--dg-text-muted)] mt-1">
集群概览
{clusterInfo?.version ? ` — Kubernetes ${clusterInfo.version}` : ''}
</p>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="icon" className="h-9 w-9 border-slate-700 text-slate-400"
<Button variant="outline" size="icon" className="h-9 w-9 border-[var(--dg-border)] text-[var(--dg-text-secondary)]"
onClick={loadData} disabled={loading}>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
</Button>
@@ -106,27 +106,27 @@ export default function ClusterOverviewPage() {
{/* Stats cards */}
<div className="grid grid-cols-3 md:grid-cols-3 gap-4">
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div><p className="text-sm text-slate-500">节点</p><p className="text-2xl font-bold text-blue-400">{nodes.length}</p></div>
<Cpu className="h-8 w-8 opacity-30 text-blue-400" />
<div><p className="text-sm text-[var(--dg-text-muted)]">节点</p><p className="text-2xl font-bold text-[var(--dg-blue)]">{nodes.length}</p></div>
<Cpu className="h-8 w-8 opacity-30 text-[var(--dg-blue)]" />
</div>
</CardContent>
</Card>
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div><p className="text-sm text-slate-500">命名空间</p><p className="text-2xl font-bold text-emerald-400">{namespaces.length}</p></div>
<Boxes className="h-8 w-8 opacity-30 text-emerald-400" />
<div><p className="text-sm text-[var(--dg-text-muted)]">命名空间</p><p className="text-2xl font-bold text-[var(--dg-green)]">{namespaces.length}</p></div>
<Boxes className="h-8 w-8 opacity-30 text-[var(--dg-green)]" />
</div>
</CardContent>
</Card>
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div><p className="text-sm text-slate-500">版本</p><p className="text-lg font-bold text-slate-300">{clusterInfo?.version || '-'}</p></div>
<Server className="h-8 w-8 opacity-30 text-slate-400" />
<div><p className="text-sm text-[var(--dg-text-muted)]">版本</p><p className="text-lg font-bold text-[var(--dg-text-secondary)]">{clusterInfo?.version || '-'}</p></div>
<Server className="h-8 w-8 opacity-30 text-[var(--dg-text-secondary)]" />
</div>
</CardContent>
</Card>
@@ -134,29 +134,29 @@ export default function ClusterOverviewPage() {
{/* Nodes table */}
<div>
<h2 className="text-lg font-semibold text-slate-100 mb-3 flex items-center gap-2">
<Cpu className="h-4 w-4 text-blue-400" />节点列表
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
<Cpu className="h-4 w-4 text-[var(--dg-blue)]" />节点列表
</h2>
<Card className="bg-slate-900 border-slate-800">
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-0">
<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">IP</TableHead>
<TableHead className="text-slate-400">OS</TableHead>
<TableHead className="text-slate-400">Kubelet</TableHead>
<TableHead className="text-slate-400">CPU</TableHead>
<TableHead className="text-slate-400">内存</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)]">IP</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">OS</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">Kubelet</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">CPU</TableHead>
<TableHead className="text-[var(--dg-text-secondary)]">内存</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow><TableCell colSpan={8} className="text-center text-slate-500 py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中...</TableCell></TableRow>
<TableRow><TableCell colSpan={8} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中...</TableCell></TableRow>
) : nodes.length === 0 ? (
<TableRow><TableCell colSpan={8} className="text-center text-slate-500 py-12"><Cpu className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无节点</TableCell></TableRow>
<TableRow><TableCell colSpan={8} className="text-center text-[var(--dg-text-muted)] py-12"><Cpu className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无节点</TableCell></TableRow>
) : (
nodes.map((n: JsonData) => {
const cpuCores = n.cpu || 0;
@@ -167,38 +167,38 @@ export default function ClusterOverviewPage() {
// Normalize to MiB
const memUsageNorm = n.mem_usage_mi?.includes('Ki') ? memUsageMi / 1024 : n.mem_usage_mi?.includes('Gi') ? memUsageMi * 1024 : memUsageMi;
const memPct = memMb > 0 ? (memUsageNorm / memMb) * 100 : 0;
const color = (pct: number) => pct > 80 ? 'bg-red-500' : pct > 60 ? 'bg-yellow-500' : 'bg-emerald-500';
const color = (pct: number) => pct > 80 ? 'bg-[var(--dg-red)]' : pct > 60 ? 'bg-[var(--dg-orange)]' : 'bg-[var(--dg-green)]';
return (
<TableRow key={n.name} className="border-slate-800 hover:bg-slate-800/30 cursor-pointer"
<TableRow key={n.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30 cursor-pointer"
onClick={() => navigate(`/kubernetes/${clusterCode}/nodes/${n.name}`)}>
<TableCell className="font-medium text-blue-400 hover:text-blue-300">{n.name}</TableCell>
<TableCell className="font-medium text-[var(--dg-blue)] hover:text-[var(--dg-blue)]">{n.name}</TableCell>
<TableCell><Badge className={getNodeRole(n)}>{n.role || 'worker'}</Badge></TableCell>
<TableCell><Badge className={n.status === 'Ready' ? 'bg-emerald-500/20 text-emerald-400' : 'bg-red-500/20 text-red-400'}>{n.status || 'Unknown'}</Badge></TableCell>
<TableCell className="text-sm text-slate-400">{n.ip || '-'}</TableCell>
<TableCell className="text-sm text-slate-400 max-w-[200px] truncate">{n.os || '-'}</TableCell>
<TableCell className="text-sm text-slate-400">{n.kubelet_version || '-'}</TableCell>
<TableCell><Badge className={n.status === 'Ready' ? 'bg-[var(--dg-green)]/20 text-[var(--dg-green)]' : 'bg-[var(--dg-red)]/20 text-[var(--dg-red)]'}>{n.status || 'Unknown'}</Badge></TableCell>
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{n.ip || '-'}</TableCell>
<TableCell className="text-sm text-[var(--dg-text-secondary)] max-w-[200px] truncate">{n.os || '-'}</TableCell>
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{n.kubelet_version || '-'}</TableCell>
<TableCell className="text-xs">
{n.cpu_usage_m ? (
<div className="flex items-center gap-1.5 min-w-[80px]">
<div className="flex-1 h-1.5 rounded-full bg-slate-700/50 overflow-hidden">
<div className="flex-1 h-1.5 rounded-full bg-[var(--dg-bg-tertiary)]/50 overflow-hidden">
<div className={`h-full rounded-full ${color(cpuPct)}`} style={{ width: `${Math.min(cpuPct, 100)}%` }} />
</div>
<span className="text-slate-400">{cpuPct.toFixed(0)}%</span>
<span className="text-[var(--dg-text-secondary)]">{cpuPct.toFixed(0)}%</span>
</div>
) : (
<span className="text-slate-500">{cpuCores} 核</span>
<span className="text-[var(--dg-text-muted)]">{cpuCores} 核</span>
)}
</TableCell>
<TableCell className="text-xs">
{n.mem_usage_mi ? (
<div className="flex items-center gap-1.5 min-w-[80px]">
<div className="flex-1 h-1.5 rounded-full bg-slate-700/50 overflow-hidden">
<div className="flex-1 h-1.5 rounded-full bg-[var(--dg-bg-tertiary)]/50 overflow-hidden">
<div className={`h-full rounded-full ${color(memPct)}`} style={{ width: `${Math.min(memPct, 100)}%` }} />
</div>
<span className="text-slate-400">{memPct.toFixed(0)}%</span>
<span className="text-[var(--dg-text-secondary)]">{memPct.toFixed(0)}%</span>
</div>
) : (
<span className="text-slate-500">{memMb}Mi</span>
<span className="text-[var(--dg-text-muted)]">{memMb}Mi</span>
)}
</TableCell>
</TableRow>
@@ -214,10 +214,10 @@ export default function ClusterOverviewPage() {
{/* Namespace list */}
<div>
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
<Boxes className="h-4 w-4 text-emerald-400" />命名空间
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)] flex items-center gap-2">
<Boxes className="h-4 w-4 text-[var(--dg-green)]" />命名空间
</h2>
<Link to={`/kubernetes/${clusterCode}/namespaces`} className="text-sm text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-1">
<Link to={`/kubernetes/${clusterCode}/namespaces`} className="text-sm text-[var(--dg-blue)] hover:text-[var(--dg-blue)] transition-colors flex items-center gap-1">
管理全部 <ChevronRight className="h-3 w-3" />
</Link>
</div>
@@ -227,14 +227,14 @@ export default function ClusterOverviewPage() {
return (
<Link key={ns}
to={`/kubernetes/${clusterCode}/namespace/${ns}`}
className="bg-slate-900 border border-slate-800 rounded-lg p-4 hover:border-slate-600 hover:bg-slate-800/50 transition-all group"
className="bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-lg p-4 hover:border-[var(--dg-border-hover)] hover:bg-[var(--dg-bg-tertiary)]/50 transition-all group"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Icon className="h-5 w-5 text-slate-500 group-hover:text-slate-300 transition-colors" />
<span className="text-sm font-medium text-slate-300 group-hover:text-slate-100 transition-colors">{ns}</span>
<Icon className="h-5 w-5 text-[var(--dg-text-muted)] group-hover:text-[var(--dg-text-secondary)] transition-colors" />
<span className="text-sm font-medium text-[var(--dg-text-secondary)] group-hover:text-[var(--dg-text-primary)] transition-colors">{ns}</span>
</div>
<ChevronRight className="h-4 w-4 text-slate-600 group-hover:text-slate-400 transition-colors" />
<ChevronRight className="h-4 w-4 text-[var(--dg-text-muted)] group-hover:text-[var(--dg-text-secondary)] transition-colors" />
</div>
</Link>
);