feat(k8s): 事件页简化、可视化编辑 P0、Deployment 五 Tab 修复

- 事件中心:去彩虹图表,改专业排名列表 + 主题变量
- 网络/配置/存储:ResourceVisualEditor 双向 YAML 同步、导入 YAML、新建
- vite manualChunks 减小 embed 主包体积
- run.sh:多副本强制 MODE=release + MySQL/Redis
- Deployment 编辑:共享 manifest、统一 saveDeploymentManifest
- 修复存储挂载 initContainers 混入 bug、补 Tolerations UI
- 服务 Tab:applyYAML 创建 + ResourceVisualEditor 编辑
- 基本信息:Pod 模板标签 + Selector 同步
This commit is contained in:
Logan.Li committed 2026-06-26 13:52:32 +08:00
1 parent 339b24a579
commit d0cf71b05a
17 files changed
+814 -497

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, Terminal, HardDrive,
Download, FileCode, Pencil, Terminal, HardDrive, Plus, Upload,
} from 'lucide-react';
import { YamlViewer } from './YamlViewer';
import { ResourceVisualEditor } from './ResourceVisualEditor';
import { YamlImportDialog } from './YamlImportDialog';
import { PodExecTerminal } from './PodExecTerminal';
import { PodFileBrowser } from './PodFileBrowser';
import { kubernetesApi } from '@/api';
@@ -27,6 +29,18 @@ type ResourceType =
| 'pvcs' | 'statefulsets' | 'daemonsets'
| 'serviceaccounts' | 'roles' | 'rolebindings';
// 支持可视化编辑/新建的资源类型 // Logan.Li
const VISUAL_RESOURCE_TYPES: ResourceType[] = ['services', 'ingresses', 'configmaps', 'secrets', 'pvcs'];
const VisualActions = ({ name, opts }: { name: string; opts: { onYaml?: (n: string) => void; onEdit?: (n: string) => void } }) => (
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-emerald-400 hover:text-emerald-300" title="可视化编辑"
onClick={() => opts.onEdit?.(name)}><Pencil className="h-3.5 w-3.5 mr-1" />编辑</Button>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-slate-400 hover:text-slate-300" title="YAML"
onClick={() => opts.onYaml?.(name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</div>
);
const RESOURCE_CONFIG: Record<ResourceType, {
title: string;
subtitle: string;
@@ -104,10 +118,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell><Badge className="bg-blue-500/20 text-blue-400 border-blue-500/30">{s.type || '-'}</Badge></TableCell>
<TableCell className="text-sm text-slate-400">{s.cluster_ip || '-'}</TableCell>
<TableCell className="text-sm text-slate-400">{s.ports || '-'}</TableCell>
<TableCell>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-blue-400 hover:text-blue-300" title="YAML 编辑"
onClick={() => opts.onEdit?.(s.name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={s.name} opts={opts} /></TableCell>
</TableRow>
),
},
@@ -122,10 +133,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell><Badge variant="outline" className="border-slate-700 text-slate-400">{i.namespace}</Badge></TableCell>
<TableCell className="text-sm text-slate-400 max-w-[250px] truncate">{hosts.length > 0 ? hosts.join(', ') : '-'}</TableCell>
<TableCell><Badge className={i.tls ? 'bg-emerald-500/20 text-emerald-400' : 'bg-slate-500/20 text-slate-400'}>{i.tls ? 'HTTPS' : 'HTTP'}</Badge></TableCell>
<TableCell>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-blue-400 hover:text-blue-300" title="YAML 编辑"
onClick={() => opts.onEdit?.(i.name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={i.name} opts={opts} /></TableCell>
</TableRow>
);
},
@@ -138,10 +146,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell className="font-medium text-slate-200">{c.name}</TableCell>
<TableCell><Badge variant="outline" className="border-slate-700 text-slate-400">{c.namespace}</Badge></TableCell>
<TableCell className="text-sm text-slate-400">{c.key_count || '-'}</TableCell>
<TableCell>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-blue-400 hover:text-blue-300" title="YAML 编辑"
onClick={() => opts.onEdit?.(c.name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={c.name} opts={opts} /></TableCell>
</TableRow>
),
},
@@ -153,10 +158,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell className="font-medium text-slate-200">{s.name}</TableCell>
<TableCell><Badge variant="outline" className="border-slate-700 text-slate-400">{s.namespace}</Badge></TableCell>
<TableCell className="text-sm text-slate-400">{s.type || '-'}</TableCell>
<TableCell>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-blue-400 hover:text-blue-300" title="YAML 编辑"
onClick={() => opts.onEdit?.(s.name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={s.name} opts={opts} /></TableCell>
</TableRow>
),
},
@@ -197,10 +199,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell><Badge className={p.phase === 'Bound' ? 'bg-emerald-500/20 text-emerald-400' : 'bg-yellow-500/20 text-yellow-400'}>{p.phase || '-'}</Badge></TableCell>
<TableCell className="text-sm text-slate-400">{p.size || '-'}</TableCell>
<TableCell className="text-sm text-slate-400">{p.storage_class || '-'}</TableCell>
<TableCell>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-blue-400 hover:text-blue-300" title="YAML 编辑"
onClick={() => opts.onEdit?.(p.name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={p.name} opts={opts} /></TableCell>
</TableRow>
),
},
@@ -338,6 +337,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 [visualItem, setVisualItem] = useState<{ resource: string; name: string; isCreate?: boolean } | null>(null);
const [showYamlImport, setShowYamlImport] = useState(false);
const [terminalPod, setTerminalPod] = useState<any>(null);
const [fileBrowserPod, setFileBrowserPod] = useState<any>(null);
@@ -405,8 +406,15 @@ export default function NamespaceResourcePage() {
};
const handleYaml = (name: string) => setYamlItem({ resource: type, name });
// 网络/配置/存储统一走 YamlViewer 编辑(清理只读字段 + CodeMirror)// Logan.Li
const handleEdit = (name: string) => setYamlItem({ resource: type, name, editMode: true });
// 网络/配置/存储走可视化编辑器,YAML 作为高级入口 // Logan.Li
const handleEdit = (name: string) => {
if (VISUAL_RESOURCE_TYPES.includes(type)) {
setVisualItem({ resource: type, name });
} else {
setYamlItem({ resource: type, name, editMode: true });
}
};
const handleCreate = () => setVisualItem({ resource: type, name: '', isCreate: true });
const handleExportCSV = () => kubernetesApi.exportAsCSV(`${type}-${clusterCode}-${namespace}.csv`, filtered);
const handleExportJSON = () => kubernetesApi.exportAsJSON(`${type}-${clusterCode}-${namespace}.json`, filtered);
@@ -426,6 +434,24 @@ export default function NamespaceResourcePage() {
autoEdit={yamlItem.editMode === true}
/>
)}
{showYamlImport && (
<YamlImportDialog
clusterCode={clusterCode || ''}
onClose={() => setShowYamlImport(false)}
onApplied={loadData}
/>
)}
{visualItem && (
<ResourceVisualEditor
clusterCode={clusterCode || ''}
namespace={namespace || ''}
resource={visualItem.resource}
name={visualItem.name}
isCreate={visualItem.isCreate}
onClose={() => setVisualItem(null)}
onSaved={loadData}
/>
)}
<div className="flex items-center gap-2 text-sm text-slate-500">
<Link to="/kubernetes" className="hover:text-slate-300 transition-colors">集群</Link>
@@ -456,6 +482,16 @@ export default function NamespaceResourcePage() {
<Button variant="outline" size="sm" className="h-9 border-slate-700 text-slate-400 text-xs" onClick={handleExportJSON} title="导出JSON">
<Download className="h-3 w-3 mr-1" />JSON
</Button>
{VISUAL_RESOURCE_TYPES.includes(type) && (
<>
<Button variant="outline" size="sm" className="h-9 border-slate-700 text-slate-400 text-xs" onClick={() => setShowYamlImport(true)}>
<Upload className="h-3 w-3 mr-1" />导入
</Button>
<Button size="sm" className="h-9 text-xs bg-blue-600 hover:bg-blue-700" onClick={handleCreate}>
<Plus className="h-3 w-3 mr-1" />新建
</Button>
</>
)}
</div>
</div>
<div className="flex items-center gap-3 flex-wrap">