feat(k8s): 统一 YAML 查看编辑并补齐 Pod/导出能力

- 抽取 YamlCodeMirror/YamlEditorToolbar,修复弹窗滚动与固定高度布局
- 对齐集群导出精简规则(Service/Ingress/Workload/PVC 注解等)
- Deployment/Pod/Service/Ingress 分资源配置:Pod 只读,支持单文件下载
- 修复 Deployment Pod YAML 弹窗未渲染,补全 PodsTab 与详情页 YAML 入口
- 命名空间列表页新增导出 YAML(tar.gz),与集群导出 API 一致
This commit is contained in:
Logan.Li committed 2026-06-26 15:25:46 +08:00
1 parent eeba677850
commit fd7b04b9d6
15 files changed
+651 -150

No files matched your search

@@ -31,6 +31,8 @@ type ResourceType =
// 支持可视化编辑/新建的资源类型 // Logan.Li
const VISUAL_RESOURCE_TYPES: ResourceType[] = ['services', 'ingresses', 'configmaps', 'secrets', 'pvcs'];
/** 支持命名空间 YAML 批量导出(对齐集群导出)// Logan.Li */
const EXPORT_YAML_TYPES: ResourceType[] = ['deployments', 'services', 'ingresses', 'configmaps', 'secrets'];
const VisualActions = ({ name, opts }: { name: string; opts: { onYaml?: (n: string) => void; onEdit?: (n: string) => void } }) => (
<div className="flex items-center gap-0.5">
@@ -71,10 +73,8 @@ const RESOURCE_CONFIG: Record<ResourceType, {
</TableCell>
<TableCell className="text-xs text-slate-500 max-w-[200px] truncate">{d.image || '-'}</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400" title="查看YAML"
onClick={() => opts.onYaml?.(d.name)}><FileCode className="h-3.5 w-3.5" /></Button>
</div>
<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?.(d.name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
</TableCell>
</TableRow>
),
@@ -318,7 +318,7 @@ export default function NamespaceResourcePage() {
const { clusterCode, namespace } = useParams<{ clusterCode: string; namespace: string }>();
const location = useLocation();
const navigate = useNavigate();
const { toast: _toast } = useToast();
const { toast } = useToast();
const type = useMemo<ResourceType>(() => {
const segments = location.pathname.split('/');
const last = segments[segments.length - 1];
@@ -417,6 +417,15 @@ export default function NamespaceResourcePage() {
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);
const handleExportYaml = async () => {
if (!clusterCode || !namespace) return;
try {
await kubernetesApi.exportNamespaceResources(clusterCode, namespace);
toast({ title: '导出完成', description: `${namespace} 资源 YAML 已开始下载` });
} catch (e) {
toast({ title: '导出失败', description: (e as Error)?.message || String(e), variant: 'destructive' });
}
};
if (!config) return <div className="text-slate-400 p-8 text-center">未知资源类型: {type}</div>;
@@ -430,7 +439,6 @@ export default function NamespaceResourcePage() {
name={yamlItem.name}
onClose={() => setYamlItem(null)}
onSaved={loadData}
cleanBeforeEdit={true}
autoEdit={yamlItem.editMode === true}
/>
)}
@@ -482,6 +490,11 @@ 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>
{EXPORT_YAML_TYPES.includes(type) && (
<Button variant="outline" size="sm" className="h-9 border-slate-700 text-slate-400 text-xs" onClick={handleExportYaml} title="导出命名空间 YAML(tar.gz)">
<Download className="h-3 w-3 mr-1" />导出YAML
</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)}>