feat(k8s): M3 Pod文件上传 + ExecInPod WebSocket协议重写
M3: Pod 文件上传 - 后端: POST .../pods/:pod/files/upload (multipart, base64分块exec写入) - 前端: PodFileBrowser 增加上传按钮 + FormData API - 路由: 新增 Kuboard (:clusterCode) + 旧 (:id) 两套路由 重大修复: ExecInPod 从 raw HTTP SPDY 改为 WebSocket channel.k8s.io - 旧方案: HTTP POST + SPDY upgrade → 无法解析响应(协议头缺失 + 帧解析复杂) - 新方案: gorilla/websocket Dialer + channel.k8s.io 子协议 → 和 ExecTerminal 一致 - 影响: 文件浏览/下载/上传全部恢复正常 修复: BusyBox ls 解析 - BusyBox 不支持 --time-style 参数,默认输出 Mon DD HH:MM 格式 - 解析器改为自动检测 GNU vs BusyBox 日期格式
This commit is contained in:
1 parent
69df073d42
commit
9ab7572b8e
11 files changed
+389
-126
No files matched your search
@@ -249,12 +249,13 @@ export const kubernetesApi = {
|
||||
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/exec`, { command, container });
|
||||
},
|
||||
// 🆕 Pod 级 exec WebSocket URL (Logan <admin@attacker.club>)
|
||||
// ws://host/ws/kubernetes-exec/:clusterId/namespaces/:ns/pods/:pod?container=&token=
|
||||
// ws://host/ws/kubernetes-exec/:clusterId/namespaces/:ns/pods/:pod?container=&command=&token=
|
||||
buildPodExecWsUrl: (
|
||||
clusterId: number | string,
|
||||
namespace: string,
|
||||
podName: string,
|
||||
container?: string
|
||||
container?: string,
|
||||
command?: string
|
||||
) => {
|
||||
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const baseUrl = import.meta.env.VITE_API_BASE_URL || `${proto}//${window.location.host}`;
|
||||
@@ -262,6 +263,7 @@ export const kubernetesApi = {
|
||||
const token = useUserStore.getState().token || '';
|
||||
const params = new URLSearchParams();
|
||||
if (container) params.set('container', container);
|
||||
if (command) params.set('command', command);
|
||||
if (token) params.set('token', token);
|
||||
const qs = params.toString();
|
||||
return `${wsBase}/ws/kubernetes-exec/${clusterId}/namespaces/${namespace}/pods/${podName}${qs ? `?${qs}` : ''}`;
|
||||
@@ -309,6 +311,33 @@ export const kubernetesApi = {
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
return resp.text();
|
||||
},
|
||||
// 上传文件到 Pod 容器内指定目录
|
||||
uploadPodFile: async (
|
||||
clusterCode: string,
|
||||
namespace: string,
|
||||
podName: string,
|
||||
targetPath: string,
|
||||
file: File,
|
||||
container?: string,
|
||||
) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('path', targetPath);
|
||||
if (container) formData.append('container', container);
|
||||
const token = useUserStore.getState().token || localStorage.getItem('token') || '';
|
||||
const baseUrl = import.meta.env.VITE_API_BASE_URL || `${window.location.protocol}//${window.location.host}`;
|
||||
const resp = await fetch(
|
||||
`${baseUrl}/api/v1/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/files/upload`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
body: formData,
|
||||
},
|
||||
);
|
||||
const json = await resp.json();
|
||||
if (!resp.ok || json.code !== 200) throw new Error(json.message || `HTTP ${resp.status}`);
|
||||
return json.data;
|
||||
},
|
||||
scaleDeployment: async (clusterCode: string, namespace: string, name: string, replicas: number) => {
|
||||
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/scale`, { replicas });
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user