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
@@ -15,7 +15,7 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import {
|
||||
Folder, FolderOpen, FileText, FileCode, FileArchive, FileImage,
|
||||
File, Download, Eye, ChevronRight, ArrowLeft, ArrowRight, ArrowUp,
|
||||
File, Download, Upload, Eye, ChevronRight, ArrowLeft, ArrowRight, ArrowUp,
|
||||
RotateCw, Loader2, Maximize2, Minimize2, X, Box, Home, HardDrive,
|
||||
AlertCircle, FileType, Binary,
|
||||
} from 'lucide-react';
|
||||
@@ -156,6 +156,10 @@ export function PodFileBrowser({
|
||||
// 预览
|
||||
const [preview, setPreview] = useState<{ path: string; content: string; loading: boolean } | null>(null);
|
||||
|
||||
// 上传
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 选中的条目(用于高亮)
|
||||
const [selected, setSelected] = useState<string>('');
|
||||
|
||||
@@ -287,6 +291,26 @@ export function PodFileBrowser({
|
||||
toast({ title: '已开始下载', description: path });
|
||||
}, [clusterCode, namespace, podName, container, toast]);
|
||||
|
||||
// 上传文件
|
||||
const handleUpload = useCallback(async (files: FileList | null) => {
|
||||
if (!files || files.length === 0) return;
|
||||
setUploading(true);
|
||||
try {
|
||||
for (const file of Array.from(files)) {
|
||||
await kubernetesApi.uploadPodFile(clusterCode, namespace, podName, currentPath, file, container || undefined);
|
||||
toast({ title: '上传成功', description: `${file.name} → ${currentPath}/${file.name}` });
|
||||
}
|
||||
// 刷新文件列表
|
||||
loadPath(currentPath);
|
||||
} catch (e: any) {
|
||||
toast({ title: '上传失败', description: e?.message || String(e), variant: 'destructive' });
|
||||
} finally {
|
||||
setUploading(false);
|
||||
// 清空 input 允许重复上传同一文件
|
||||
if (fileInputRef.current) fileInputRef.current.value = '';
|
||||
}
|
||||
}, [clusterCode, namespace, podName, container, currentPath, loadPath, toast]);
|
||||
|
||||
const breadcrumbs = useMemo(() => pathSegments(currentPath), [currentPath]);
|
||||
|
||||
return (
|
||||
@@ -331,6 +355,23 @@ export function PodFileBrowser({
|
||||
<Button variant="ghost" size="sm" className="h-8 px-2" onClick={() => loadPath(currentPath)} title="刷新">
|
||||
<RotateCw className={cn('h-3.5 w-3.5', loading && 'animate-spin')} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
disabled={uploading}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
title="上传文件"
|
||||
>
|
||||
{uploading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Upload className="h-3.5 w-3.5" />}
|
||||
</Button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
className="hidden"
|
||||
multiple
|
||||
onChange={(e) => handleUpload(e.target.files)}
|
||||
/>
|
||||
<button
|
||||
onClick={() => setFullscreen(!fullscreen)}
|
||||
className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]"
|
||||
|
||||
Reference in new issue
Block a user