feat(k8s): 实时日志流 + WebSocket exec 交互终端 + 前端流式日志查看器

后端:
  - GetPodLogsStream: 新增 K8s follow=true 流式日志读取
  - GetPodLogs handler: 支持 stream=true → chunked transfer 实时推流
  - ExecTerminal: WebSocket → K8s SPDY exec 双向代理 (交互式 bash)
  - 路由: GET /ws/k8s-exec/:id/namespaces/:ns/pods/:pod (WebSocket)

前端 DeploymentDetail:
  - 新增「实时流」按钮: fetch ReadableStream + TextDecoder 逐块渲染
  - 日志内容自动滚动追加

CD/CI 镜像更新:
  - POST /update-image 已可用 (前端表单+后端 API 均完成)
This commit is contained in:
attacker committed 2026-06-19 07:49:32 +00:00
1 parent 1a913549fc
commit 77045622fc
4 files changed
+201 -5

No files matched your search

@@ -388,9 +388,38 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
<p>点击 Pod 列表中的「日志」按钮查看</p>
</div>
) : (
<pre className="bg-slate-950 rounded-lg p-4 text-xs text-slate-300 font-mono overflow-auto max-h-[500px] whitespace-pre-wrap">
{logs || '无日志内容'}
</pre>
<div>
<div className="flex items-center gap-2 mb-2">
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300 text-xs h-7"
onClick={async () => {
setLogs('');
// Stream mode
const token = localStorage.getItem('token') || '';
const url = `${window.location.protocol}//${window.location.host}/api/v1/k8s-clusters/${clusterId}/namespaces/${namespace}/pods/${logPod}/logs?tail=${logTail}&stream=true`;
try {
const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const reader = resp.body?.getReader();
if (!reader) return;
const decoder = new TextDecoder();
let streamLogs = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
streamLogs += decoder.decode(value, { stream: true });
setLogs(streamLogs);
}
} catch (e) {
setLogs('流式连接中断');
}
}}>
<RefreshCw className="h-3 w-3 mr-1" />实时流
</Button>
<span className="text-xs text-slate-500">{logPod}</span>
</div>
<pre className="bg-slate-950 rounded-lg p-4 text-xs text-slate-300 font-mono overflow-auto max-h-[500px] whitespace-pre-wrap">
{logs || '无日志内容'}
</pre>
</div>
)}
</CardContent>
</Card>