feat: Agent CLI 与终端审计回放全面优化
- Agent CLI: 多轮对话、SSH 长连接、流式推理、Evolver 按用户隔离、历史侧栏 - 终端审计: 回放播放器、命令书签、筛选分页导出、命令输出统计、录制落盘修复 - 修复 session_commands 表迁移、Kill 竞态导致录制丢失、无录制会话回放提示 - 审计历史页只读(移除删除),移除回放弹框录制搜索
This commit is contained in:
1 parent
9a3dd304f7
commit
1acb0c49af
81 files changed
+3916
-1655
No files matched your search
@@ -27,11 +27,11 @@ type ViewMode = 'view' | 'yaml';
|
||||
|
||||
function StatCard({ label, value, sub, color }: { label: string; value: React.ReactNode; sub?: string; color?: string }) {
|
||||
return (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="p-3">
|
||||
<p className="text-xs text-slate-500">{label}</p>
|
||||
<p className="text-base font-bold mt-0.5" style={{ color: color || '#e2e8f0' }}>{value}</p>
|
||||
{sub && <p className="text-[10px] text-slate-600 mt-0.5">{sub}</p>}
|
||||
<p className="text-xs text-[var(--dg-text-muted)]">{label}</p>
|
||||
<p className="text-base font-bold mt-0.5" style={{ color: color || 'var(--dg-text-primary)' }}>{value}</p>
|
||||
{sub && <p className="text-[10px] text-[var(--dg-text-muted)] mt-0.5">{sub}</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -40,11 +40,11 @@ function StatCard({ label, value, sub, color }: { label: string; value: React.Re
|
||||
// Helper: render key-value pairs as badges
|
||||
function LabelBadges({ labels }: { labels: Record<string, string> | undefined }) {
|
||||
const entries = Object.entries(labels || {});
|
||||
if (entries.length === 0) return <span className="text-xs text-slate-600">无</span>;
|
||||
if (entries.length === 0) return <span className="text-xs text-[var(--dg-text-muted)]">无</span>;
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{entries.map(([k, v]) => (
|
||||
<Badge key={k} variant="outline" className="border-slate-700 text-slate-400 text-[10px] font-mono">
|
||||
<Badge key={k} variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-[10px] font-mono">
|
||||
{k}={v}
|
||||
</Badge>
|
||||
))}
|
||||
@@ -192,46 +192,46 @@ export default function WorkloadViewPage() {
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-slate-200" onClick={() => navigate(-1)}>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]" onClick={() => navigate(-1)}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Boxes className="h-5 w-5 text-blue-400" />
|
||||
<Boxes className="h-5 w-5 text-[var(--dg-blue)]" />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-lg font-bold text-slate-100">{name}</h1>
|
||||
<Badge variant="outline" className="border-slate-700 text-slate-400">{kind}</Badge>
|
||||
<Badge variant="outline" className="border-slate-700 text-slate-400">{namespace}</Badge>
|
||||
<h1 className="text-lg font-bold text-[var(--dg-text-primary)]">{name}</h1>
|
||||
<Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{kind}</Badge>
|
||||
<Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{namespace}</Badge>
|
||||
{isHealthy
|
||||
? <Badge className="bg-emerald-500/20 text-emerald-400 border-emerald-500/30">健康</Badge>
|
||||
: <Badge className="bg-amber-500/20 text-amber-400 border-amber-500/30">更新中</Badge>}
|
||||
? <Badge className="bg-[var(--dg-green)]/20 text-[var(--dg-green)] border-[var(--dg-green)]/30">健康</Badge>
|
||||
: <Badge className="bg-[var(--dg-orange)]/20 text-[var(--dg-orange)] border-[var(--dg-orange)]/30">更新中</Badge>}
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-0.5">集群: {clusterCode}</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] mt-0.5">集群: {clusterCode}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Mode tabs */}
|
||||
<div className="flex items-center gap-1 bg-slate-900 border border-slate-800 rounded-lg p-0.5">
|
||||
<div className="flex items-center gap-1 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-lg p-0.5">
|
||||
<button
|
||||
className={`px-3 py-1 text-xs rounded transition-colors ${mode === 'view' ? 'bg-blue-500/20 text-blue-400' : 'text-slate-400 hover:text-slate-200'}`}
|
||||
className={`px-3 py-1 text-xs rounded transition-colors ${mode === 'view' ? 'bg-[var(--dg-blue)]/20 text-[var(--dg-blue)]' : 'text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]'}`}
|
||||
onClick={() => handleModeChange('view')}
|
||||
>查看</button>
|
||||
<button
|
||||
className={`px-3 py-1 text-xs rounded transition-colors ${mode === 'yaml' ? 'bg-blue-500/20 text-blue-400' : 'text-slate-400 hover:text-slate-200'}`}
|
||||
className={`px-3 py-1 text-xs rounded transition-colors ${mode === 'yaml' ? 'bg-[var(--dg-blue)]/20 text-[var(--dg-blue)]' : 'text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]'}`}
|
||||
onClick={() => handleModeChange('yaml')}
|
||||
>YAML</button>
|
||||
</div>
|
||||
{/* Actions */}
|
||||
{isSts && (
|
||||
<Button variant="outline" size="sm" className="h-8 border-slate-700 text-slate-300 hover:text-slate-100"
|
||||
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
|
||||
onClick={() => { setScaleValue(replicas); setScaleModal(true); }}>
|
||||
<Boxes className="h-3.5 w-3.5 mr-1" />伸缩
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline" size="sm" className="h-8 border-slate-700 text-slate-300 hover:text-slate-100"
|
||||
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
|
||||
disabled={actionLoading} onClick={handleRestart}>
|
||||
<RotateCcw className="h-3.5 w-3.5 mr-1" />{actionLoading ? '重启中...' : '重启'}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="h-8 border-slate-700 text-slate-300 hover:text-slate-100"
|
||||
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
|
||||
onClick={() => setYamlModal(true)}>
|
||||
<FileCode className="h-3.5 w-3.5 mr-1" />YAML
|
||||
</Button>
|
||||
@@ -239,9 +239,9 @@ export default function WorkloadViewPage() {
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center text-slate-500 py-20">加载中...</div>
|
||||
<div className="text-center text-[var(--dg-text-muted)] py-20">加载中...</div>
|
||||
) : mode === 'yaml' ? (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="p-4">
|
||||
<YamlViewer
|
||||
clusterCode={clusterCode}
|
||||
@@ -263,45 +263,45 @@ export default function WorkloadViewPage() {
|
||||
<StatCard label="副本就绪" value={`${readyReplicas}/${replicas}`} color={isHealthy ? STATUS_COLORS.green : STATUS_COLORS.orange} sub={isHealthy ? '全部就绪' : '部分未就绪'} />
|
||||
<StatCard label="当前副本" value={currentReplicas} color={STATUS_COLORS.blue} />
|
||||
<StatCard label="已更新副本" value={updatedReplicas} color={STATUS_COLORS.green} />
|
||||
<StatCard label="服务名" value={spec.serviceName || '-'} color="#94a3b8" />
|
||||
<StatCard label="Pod 数量" value={pods.length} color="#94a3b8" />
|
||||
<StatCard label="创建时间" value={<span className="text-xs">{formatK8sTime(manifest?.metadata?.creationTimestamp)}</span>} color="#94a3b8" />
|
||||
<StatCard label="服务名" value={spec.serviceName || '-'} color="var(--dg-text-secondary)" />
|
||||
<StatCard label="Pod 数量" value={pods.length} color="var(--dg-text-secondary)" />
|
||||
<StatCard label="创建时间" value={<span className="text-xs">{formatK8sTime(manifest?.metadata?.creationTimestamp)}</span>} color="var(--dg-text-secondary)" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StatCard label="期望调度" value={desiredScheduled} color={STATUS_COLORS.blue} />
|
||||
<StatCard label="当前调度" value={currentScheduled} color="#94a3b8" />
|
||||
<StatCard label="当前调度" value={currentScheduled} color="var(--dg-text-secondary)" />
|
||||
<StatCard label="就绪数量" value={numberReady} color={isHealthy ? STATUS_COLORS.green : STATUS_COLORS.orange} />
|
||||
<StatCard label="可用数量" value={numberAvailable} color={STATUS_COLORS.green} />
|
||||
<StatCard label="已更新" value={numberUpdated} color="#94a3b8" />
|
||||
<StatCard label="创建时间" value={<span className="text-xs">{formatK8sTime(manifest?.metadata?.creationTimestamp)}</span>} color="#94a3b8" />
|
||||
<StatCard label="已更新" value={numberUpdated} color="var(--dg-text-secondary)" />
|
||||
<StatCard label="创建时间" value={<span className="text-xs">{formatK8sTime(manifest?.metadata?.creationTimestamp)}</span>} color="var(--dg-text-secondary)" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Update Strategy + Selector */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-slate-200 flex items-center gap-2">
|
||||
<Settings className="h-4 w-4 text-amber-400" />更新策略
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Settings className="h-4 w-4 text-[var(--dg-orange)]" />更新策略
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-2 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">策略类型</span>
|
||||
<Badge variant="outline" className="border-slate-700 text-slate-300">{strategyType}</Badge>
|
||||
<span className="text-[var(--dg-text-muted)]">策略类型</span>
|
||||
<Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{strategyType}</Badge>
|
||||
</div>
|
||||
{strategyType === 'RollingUpdate' && isSts && updateStrategy.rollingUpdate && (
|
||||
<>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">分区 (partition)</span>
|
||||
<span className="text-slate-300 font-mono">{updateStrategy.rollingUpdate.partition ?? 0}</span>
|
||||
<span className="text-[var(--dg-text-muted)]">分区 (partition)</span>
|
||||
<span className="text-[var(--dg-text-secondary)] font-mono">{updateStrategy.rollingUpdate.partition ?? 0}</span>
|
||||
</div>
|
||||
{updateStrategy.rollingUpdate.maxUnavailable && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">最大不可用</span>
|
||||
<span className="text-slate-300 font-mono">{typeof updateStrategy.rollingUpdate.maxUnavailable === 'object' ? JSON.stringify(updateStrategy.rollingUpdate.maxUnavailable) : updateStrategy.rollingUpdate.maxUnavailable}</span>
|
||||
<span className="text-[var(--dg-text-muted)]">最大不可用</span>
|
||||
<span className="text-[var(--dg-text-secondary)] font-mono">{typeof updateStrategy.rollingUpdate.maxUnavailable === 'object' ? JSON.stringify(updateStrategy.rollingUpdate.maxUnavailable) : updateStrategy.rollingUpdate.maxUnavailable}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -310,52 +310,52 @@ export default function WorkloadViewPage() {
|
||||
<>
|
||||
{updateStrategy.rollingUpdate.maxUnavailable && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">最大不可用</span>
|
||||
<span className="text-slate-300 font-mono">{typeof updateStrategy.rollingUpdate.maxUnavailable === 'object' ? JSON.stringify(updateStrategy.rollingUpdate.maxUnavailable) : updateStrategy.rollingUpdate.maxUnavailable}</span>
|
||||
<span className="text-[var(--dg-text-muted)]">最大不可用</span>
|
||||
<span className="text-[var(--dg-text-secondary)] font-mono">{typeof updateStrategy.rollingUpdate.maxUnavailable === 'object' ? JSON.stringify(updateStrategy.rollingUpdate.maxUnavailable) : updateStrategy.rollingUpdate.maxUnavailable}</span>
|
||||
</div>
|
||||
)}
|
||||
{updateStrategy.rollingUpdate.maxSurge && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">最大 surge</span>
|
||||
<span className="text-slate-300 font-mono">{typeof updateStrategy.rollingUpdate.maxSurge === 'object' ? JSON.stringify(updateStrategy.rollingUpdate.maxSurge) : updateStrategy.rollingUpdate.maxSurge}</span>
|
||||
<span className="text-[var(--dg-text-muted)]">最大 surge</span>
|
||||
<span className="text-[var(--dg-text-secondary)] font-mono">{typeof updateStrategy.rollingUpdate.maxSurge === 'object' ? JSON.stringify(updateStrategy.rollingUpdate.maxSurge) : updateStrategy.rollingUpdate.maxSurge}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isSts && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Pod 管理策略</span>
|
||||
<span className="text-slate-300 font-mono">{spec.podManagementPolicy || 'OrderedReady'}</span>
|
||||
<span className="text-[var(--dg-text-muted)]">Pod 管理策略</span>
|
||||
<span className="text-[var(--dg-text-secondary)] font-mono">{spec.podManagementPolicy || 'OrderedReady'}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">修订历史限制</span>
|
||||
<span className="text-slate-300 font-mono">{spec.revisionHistoryLimit ?? 10}</span>
|
||||
<span className="text-[var(--dg-text-muted)]">修订历史限制</span>
|
||||
<span className="text-[var(--dg-text-secondary)] font-mono">{spec.revisionHistoryLimit ?? 10}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-slate-200 flex items-center gap-2">
|
||||
<Tag className="h-4 w-4 text-purple-400" />选择器与标签
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Tag className="h-4 w-4 text-[var(--dg-purple)]" />选择器与标签
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-3 text-xs">
|
||||
<div>
|
||||
<p className="text-slate-500 mb-1.5">Selector (matchLabels)</p>
|
||||
<p className="text-[var(--dg-text-muted)] mb-1.5">Selector (matchLabels)</p>
|
||||
<LabelBadges labels={selectorLabels} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-slate-500 mb-1.5">Labels</p>
|
||||
<p className="text-[var(--dg-text-muted)] mb-1.5">Labels</p>
|
||||
<LabelBadges labels={labels} />
|
||||
</div>
|
||||
{Object.keys(annotations).length > 0 && (
|
||||
<div>
|
||||
<p className="text-slate-500 mb-1.5">Annotations</p>
|
||||
<p className="text-[var(--dg-text-muted)] mb-1.5">Annotations</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{Object.entries(annotations).slice(0, 8).map(([k, _v]) => (
|
||||
<Badge key={k} variant="outline" className="border-slate-700 text-slate-500 text-[10px] font-mono max-w-[300px] truncate">
|
||||
<Badge key={k} variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-muted)] text-[10px] font-mono max-w-[300px] truncate">
|
||||
{k}
|
||||
</Badge>
|
||||
))}
|
||||
@@ -368,20 +368,20 @@ export default function WorkloadViewPage() {
|
||||
|
||||
{/* VolumeClaimTemplates (StatefulSet only) */}
|
||||
{isSts && vcts.length > 0 && (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-slate-200 flex items-center gap-2">
|
||||
<Database className="h-4 w-4 text-cyan-400" />存储卷声明模板 (VolumeClaimTemplates)
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Database className="h-4 w-4 text-[var(--dg-cyan)]" />存储卷声明模板 (VolumeClaimTemplates)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-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">大小</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>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -390,11 +390,11 @@ export default function WorkloadViewPage() {
|
||||
const storageClass = vct.spec?.storageClassName || vct.spec?.storageClassName || '-';
|
||||
const size = vct.spec?.resources?.requests?.storage || '-';
|
||||
return (
|
||||
<TableRow key={idx} className="border-slate-800">
|
||||
<TableCell className="font-medium text-slate-200">{vct.metadata?.name || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-slate-400">{accessModes || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-slate-400">{storageClass}</TableCell>
|
||||
<TableCell className="text-sm text-slate-300 font-mono">{size}</TableCell>
|
||||
<TableRow key={idx} className="border-[var(--dg-border)]">
|
||||
<TableCell className="font-medium text-[var(--dg-text-primary)]">{vct.metadata?.name || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{accessModes || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{storageClass}</TableCell>
|
||||
<TableCell className="text-sm text-[var(--dg-text-secondary)] font-mono">{size}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
@@ -405,16 +405,16 @@ export default function WorkloadViewPage() {
|
||||
)}
|
||||
|
||||
{/* Container template */}
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-slate-200 flex items-center gap-2">
|
||||
<Container className="h-4 w-4 text-blue-400" />容器模板(Pod Template)
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Container className="h-4 w-4 text-[var(--dg-blue)]" />容器模板(Pod Template)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
{initContainers.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<p className="text-[11px] text-slate-500 mb-2 uppercase tracking-wide">Init Containers</p>
|
||||
<p className="text-[11px] text-[var(--dg-text-muted)] mb-2 uppercase tracking-wide">Init Containers</p>
|
||||
<div className="space-y-2">
|
||||
{initContainers.map((c: JsonData, idx: number) => (
|
||||
<ContainerInfo key={`init-${idx}`} c={c} />
|
||||
@@ -423,10 +423,10 @@ export default function WorkloadViewPage() {
|
||||
</div>
|
||||
)}
|
||||
{containers.length === 0 ? (
|
||||
<p className="text-xs text-slate-500 py-3">暂无容器数据,请查看 YAML</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] py-3">暂无容器数据,请查看 YAML</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<p className="text-[11px] text-slate-500 mb-1 uppercase tracking-wide">Containers</p>
|
||||
<p className="text-[11px] text-[var(--dg-text-muted)] mb-1 uppercase tracking-wide">Containers</p>
|
||||
{containers.map((c: JsonData, idx: number) => (
|
||||
<ContainerInfo key={idx} c={c} />
|
||||
))}
|
||||
@@ -436,45 +436,45 @@ export default function WorkloadViewPage() {
|
||||
</Card>
|
||||
|
||||
{/* Pod list */}
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-slate-200 flex items-center gap-2">
|
||||
<Activity className="h-4 w-4 text-emerald-400" />Pod 实例 ({pods.length})
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Activity className="h-4 w-4 text-[var(--dg-green)]" />Pod 实例 ({pods.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-slate-800 hover:bg-transparent">
|
||||
<TableHead className="text-slate-400">Pod 名称</TableHead>
|
||||
<TableHead className="text-slate-400">状态</TableHead>
|
||||
<TableHead className="text-slate-400">所在节点</TableHead>
|
||||
<TableHead className="text-slate-400">Pod IP</TableHead>
|
||||
<TableHead className="text-slate-400">重启</TableHead>
|
||||
<TableHead className="text-slate-400">操作</TableHead>
|
||||
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">Pod 名称</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">状态</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">所在节点</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">Pod IP</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">重启</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pods.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={6} className="text-center text-slate-500 py-8">暂无 Pod</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={6} className="text-center text-[var(--dg-text-muted)] py-8">暂无 Pod</TableCell></TableRow>
|
||||
) : pods.map((pod: JsonData) => (
|
||||
<TableRow key={pod.name} className="border-slate-800 hover:bg-slate-800/30">
|
||||
<TableCell className="font-medium text-slate-200">{pod.name}</TableCell>
|
||||
<TableRow key={pod.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
||||
<TableCell className="font-medium text-[var(--dg-text-primary)]">{pod.name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge style={{ background: `${statusColor(pod.status)}22`, color: statusColor(pod.status), border: `1px solid ${statusColor(pod.status)}44` }}>
|
||||
{pod.status || 'Unknown'}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-slate-400">{pod.node_name || pod.node || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-slate-400 font-mono">{pod.ip || pod.pod_ip || '-'}</TableCell>
|
||||
<TableCell className={(pod.restart_count || 0) > 0 ? 'text-yellow-400' : 'text-slate-500'}>{pod.restart_count || 0}</TableCell>
|
||||
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{pod.node_name || pod.node || '-'}</TableCell>
|
||||
<TableCell className="text-sm text-[var(--dg-text-secondary)] font-mono">{pod.ip || pod.pod_ip || '-'}</TableCell>
|
||||
<TableCell className={(pod.restart_count || 0) > 0 ? 'text-[var(--dg-orange)]' : 'text-[var(--dg-text-muted)]'}>{pod.restart_count || 0}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/10"
|
||||
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-green)] hover:text-[var(--dg-green)] hover:bg-[var(--dg-green)]/10"
|
||||
onClick={() => setTerminalPod(pod)}>
|
||||
<Terminal className="h-3.5 w-3.5 mr-1" />终端
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-amber-400 hover:text-amber-300 hover:bg-amber-500/10"
|
||||
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-orange)] hover:text-[var(--dg-orange)] hover:bg-[var(--dg-orange)]/10"
|
||||
onClick={() => setFileBrowserPod(pod)}>
|
||||
<HardDrive className="h-3.5 w-3.5 mr-1" />文件浏览器
|
||||
</Button>
|
||||
@@ -492,20 +492,20 @@ export default function WorkloadViewPage() {
|
||||
{/* Scale modal */}
|
||||
{scaleModal && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center" onClick={() => setScaleModal(false)}>
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-lg p-6 w-80" onClick={e => e.stopPropagation()}>
|
||||
<h3 className="text-sm font-semibold text-slate-200 mb-4">伸缩 StatefulSet</h3>
|
||||
<div className="bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-lg p-6 w-80" onClick={e => e.stopPropagation()}>
|
||||
<h3 className="text-sm font-semibold text-[var(--dg-text-primary)] mb-4">伸缩 StatefulSet</h3>
|
||||
<div className="flex items-center justify-center gap-4 mb-4">
|
||||
<Button variant="outline" size="icon" className="h-9 w-9" onClick={() => setScaleValue(Math.max(0, scaleValue - 1))}>
|
||||
<Minus className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="text-2xl font-bold text-slate-100 w-12 text-center">{scaleValue}</span>
|
||||
<span className="text-2xl font-bold text-[var(--dg-text-primary)] w-12 text-center">{scaleValue}</span>
|
||||
<Button variant="outline" size="icon" className="h-9 w-9" onClick={() => setScaleValue(scaleValue + 1)}>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" className="flex-1 border-slate-700 text-slate-400" onClick={() => setScaleModal(false)}>取消</Button>
|
||||
<Button size="sm" className="flex-1 bg-blue-600 hover:bg-blue-500" disabled={actionLoading} onClick={handleScale}>
|
||||
<Button variant="outline" size="sm" className="flex-1 border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={() => setScaleModal(false)}>取消</Button>
|
||||
<Button size="sm" className="flex-1 bg-[var(--dg-blue)] hover:opacity-90" disabled={actionLoading} onClick={handleScale}>
|
||||
{actionLoading ? '执行中...' : '确认'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -554,23 +554,23 @@ export default function WorkloadViewPage() {
|
||||
// Container info sub-component
|
||||
function ContainerInfo({ c }: { c: JsonData}) {
|
||||
return (
|
||||
<div className="border border-slate-800 rounded-lg p-3 bg-slate-950/30">
|
||||
<div className="border border-[var(--dg-border)] rounded-lg p-3 bg-[var(--dg-bg-primary)]/30">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Container className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-sm font-semibold text-slate-200">{c.name}</span>
|
||||
<Container className="h-4 w-4 text-[var(--dg-blue)]" />
|
||||
<span className="text-sm font-semibold text-[var(--dg-text-primary)]">{c.name}</span>
|
||||
</div>
|
||||
<code className="text-[11px] text-slate-400 font-mono truncate max-w-[400px]">{c.image}</code>
|
||||
<code className="text-[11px] text-[var(--dg-text-secondary)] font-mono truncate max-w-[400px]">{c.image}</code>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 text-xs">
|
||||
<div className="text-slate-400">
|
||||
CPU: <span className="text-slate-200 font-mono">{c.resources?.requests?.cpu || '-'}/{c.resources?.limits?.cpu || '-'}</span>
|
||||
<div className="text-[var(--dg-text-secondary)]">
|
||||
CPU: <span className="text-[var(--dg-text-primary)] font-mono">{c.resources?.requests?.cpu || '-'}/{c.resources?.limits?.cpu || '-'}</span>
|
||||
</div>
|
||||
<div className="text-slate-400">
|
||||
内存: <span className="text-slate-200 font-mono">{c.resources?.requests?.memory || '-'}/{c.resources?.limits?.memory || '-'}</span>
|
||||
<div className="text-[var(--dg-text-secondary)]">
|
||||
内存: <span className="text-[var(--dg-text-primary)] font-mono">{c.resources?.requests?.memory || '-'}/{c.resources?.limits?.memory || '-'}</span>
|
||||
</div>
|
||||
<div className="text-slate-400">
|
||||
端口: <span className="text-slate-200 font-mono">
|
||||
<div className="text-[var(--dg-text-secondary)]">
|
||||
端口: <span className="text-[var(--dg-text-primary)] font-mono">
|
||||
{(c.ports || []).map((p: JsonData) => p.containerPort || p.port).join(', ') || '-'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user