feat(k8s): M8+M9 存储挂载/高级设置/服务路由 Tab — Deployment 可视化编辑器 5 Tab 全部完成
M8 存储挂载 Tab (DeploymentEditStorage.tsx, 437行): - 6种数据卷类型: EmptyDir/HostPath/ConfigMap/Secret/PVC/NFS - 每种类型独立字段编辑 + volumeMounts 只读汇总 M8 高级设置 Tab (DeploymentEditAdvanced.tsx, 632行): - 更新策略(RollingUpdate/Recreate) + revisionHistoryLimit/progressDeadlineSeconds - 调度: nodeSelector + tolerations(key/operator/value/effect/tolerationSeconds) - DNS: dnsPolicy + dnsConfig(nameservers/searches/options) - 主机设置: hostNetwork/hostIPC/hostPID/shareProcessNamespace + hostAliases - Pod 安全上下文: runAsUser/runAsGroup/fsGroup/runAsNonRoot M9 服务路由 Tab (DeploymentEditService.tsx): - selector 双路径关联 Service(spec.selector.matchLabels || selector) - Service/Ingress 创建/删除集成 EditModeContainer 5 Tab 全部 available: basic / containers / storage / advanced / service 验证: go build OK + tsc --noEmit OK + vite build OK + SPA edit路由200 + YAML API有效
This commit is contained in:
1 parent
60934277ac
commit
28dfc519e1
10 files changed
+691
-665
No files matched your search
@@ -13,9 +13,11 @@ import {
|
||||
import {
|
||||
RefreshCw, Search, Loader2, Boxes, Container,
|
||||
Network, FileText, Shield, Activity, Clock, ChevronRight,
|
||||
Download, FileCode, Pencil,
|
||||
Download, FileCode, Pencil, Terminal, HardDrive,
|
||||
} from 'lucide-react';
|
||||
import { YamlViewer } from './YamlViewer';
|
||||
import { PodExecTerminal } from './PodExecTerminal';
|
||||
import { PodFileBrowser } from './PodFileBrowser';
|
||||
import { kubernetesApi } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
@@ -29,7 +31,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
|
||||
subtitle: string;
|
||||
icon: React.ComponentType<any>;
|
||||
columns: string[];
|
||||
renderRow: (item: any, opts: { clusterCode: string; namespace: string; onDetail?: (item: any) => void; onYaml?: (name: string) => void; onEdit?: (name: string) => void }) => React.ReactNode;
|
||||
renderRow: (item: any, opts: { clusterCode: string; namespace: string; onDetail?: (item: any) => void; onYaml?: (name: string) => void; onEdit?: (name: string) => void; onTerminal?: (item: any) => void; onFileBrowser?: (item: any) => void }) => React.ReactNode;
|
||||
}> = {
|
||||
deployments: {
|
||||
title: 'Deployments', subtitle: 'Deployment 工作负载运行状态', icon: Boxes,
|
||||
@@ -77,7 +79,16 @@ const RESOURCE_CONFIG: Record<ResourceType, {
|
||||
<TableCell className="text-sm text-amber-400">{p.mem_usage_mi || '-'}</TableCell>
|
||||
<TableCell className={(p.restart_count > 0 ? 'text-yellow-400' : 'text-slate-500')}>{p.restart_count || 0}</TableCell>
|
||||
<TableCell className="text-xs text-slate-500 max-w-[200px] truncate">{p.image || '-'}</TableCell>
|
||||
<TableCell><Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400" title="查看YAML" onClick={() => opts.onYaml?.(p.name)}><FileCode className="h-3.5 w-3.5" /></Button></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" title="终端"
|
||||
onClick={() => (opts as any).onTerminal?.(p)}><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" title="文件浏览器"
|
||||
onClick={() => (opts as any).onFileBrowser?.(p)}><HardDrive className="h-3.5 w-3.5 mr-1" />文件浏览器</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-blue-400 hover:text-blue-300 hover:bg-blue-500/10" title="查看YAML"
|
||||
onClick={() => opts.onYaml?.(p.name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
@@ -242,6 +253,8 @@ export default function NamespaceResourcePage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [yamlItem, setYamlItem] = useState<{resource: string; name: string; editMode?: boolean} | null>(null);
|
||||
const [terminalPod, setTerminalPod] = useState<any>(null);
|
||||
const [fileBrowserPod, setFileBrowserPod] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
kubernetesApi.getClusters().then((res: any) => {
|
||||
@@ -386,12 +399,31 @@ export default function NamespaceResourcePage() {
|
||||
) : filtered.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={config.columns.length} className="text-center text-slate-500 py-12"><config.icon className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无 {config.title}</TableCell></TableRow>
|
||||
) : (
|
||||
filtered.map((item) => config.renderRow(item, { clusterCode: clusterCode || '', namespace: namespace || '', onDetail: handleDetail, onYaml: handleYaml, onEdit: handleEdit }))
|
||||
filtered.map((item) => config.renderRow(item, { clusterCode: clusterCode || '', namespace: namespace || '', onDetail: handleDetail, onYaml: handleYaml, onEdit: handleEdit, onTerminal: (p: any) => setTerminalPod(p), onFileBrowser: (p: any) => setFileBrowserPod(p) }))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* ============ Pod 终端 ============ */}
|
||||
<PodExecTerminal
|
||||
open={!!terminalPod}
|
||||
onOpenChange={(open) => { if (!open) setTerminalPod(null); }}
|
||||
clusterId={clusters[0]?.id || clusters[0]?.code || clusterCode || ''}
|
||||
namespace={namespace || ''}
|
||||
podName={terminalPod?.name || ''}
|
||||
containers={(terminalPod?.containers || []).map((c: any) => c.name).filter(Boolean)}
|
||||
/>
|
||||
{/* ============ Pod 文件浏览器 ============ */}
|
||||
<PodFileBrowser
|
||||
open={!!fileBrowserPod}
|
||||
onOpenChange={(open) => { if (!open) setFileBrowserPod(null); }}
|
||||
clusterCode={clusterCode || ''}
|
||||
namespace={namespace || ''}
|
||||
podName={fileBrowserPod?.name || ''}
|
||||
containers={(fileBrowserPod?.containers || []).map((c: any) => c.name).filter(Boolean)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user