fix(k8s): 可视化编辑器 YAML 解析失败(managedFields 清理)

- 先解析原始 YAML 再对象级剥离,避免 cleanYAML 破坏结构
- managedFields 从 METADATA_LINE_KEYS 移除,改整块删除含数组项
- YamlViewer 同步使用 parseEditableManifest
This commit is contained in:
Logan.Li committed 2026-06-26 14:53:20 +08:00
1 parent 21d40b186a
commit 5fad2db0c6
101 files changed
+195 -146

No files matched your search

@@ -13,7 +13,7 @@ import { yaml } from '@codemirror/lang-yaml';
import { oneDark } from '@codemirror/theme-one-dark';
import { cleanYAML } from './k8s-yaml-utils';
import { createEmptyManifest } from './resource-create-templates';
import { parseManifest, dumpManifest, prepareManifestForSave } from './k8s-manifest-utils';
import { parseManifest, parseEditableManifest, dumpManifest, prepareManifestForSave } from './k8s-manifest-utils';
interface ResourceVisualEditorProps {
clusterCode: string;
@@ -46,10 +46,10 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
return;
}
const text = await kubernetesApi.getResourceYaml(clusterCode, namespace, resource, name);
const cleaned = cleanYAML(text);
setRawYaml(text);
setEditYaml(cleaned);
const parsed = await parseManifest(cleaned);
// 先解析原始 YAML,再用对象级剥离;cleanYAML 会破坏 managedFields 导致解析失败 // Logan.Li
const { manifest: parsed, yaml: editableYaml } = await parseEditableManifest(text);
setEditYaml(editableYaml);
setManifest(parsed);
if (!parsed) {
setMode('yaml');
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
import { useToast } from '@/hooks/use-toast';
import { kubernetesApi } from '@/api';
import { cleanYAML } from './k8s-yaml-utils';
import { parseEditableManifest } from './k8s-manifest-utils';
import CodeMirror from '@uiw/react-codemirror';
import { yaml } from '@codemirror/lang-yaml';
import { oneDark } from '@codemirror/theme-one-dark';
@@ -40,10 +41,16 @@ export function YamlViewer({ clusterCode, namespace, resource, name, onClose, on
// autoEdit 时隐式启用清理(编辑入口必须有清理,否则 status/managedFields 会被 K8s 拒绝)
const effectiveClean = cleanBeforeEdit || autoEdit;
const toEditableYaml = async (text: string) => {
if (!effectiveClean) return text;
const { yaml } = await parseEditableManifest(text);
return yaml;
};
const loadYaml = async () => {
if (initialYaml) {
setRawYaml(initialYaml);
setEditYaml(effectiveClean ? cleanYAML(initialYaml) : initialYaml);
setEditYaml(await toEditableYaml(initialYaml));
setLoading(false);
return;
}
@@ -51,7 +58,7 @@ export function YamlViewer({ clusterCode, namespace, resource, name, onClose, on
try {
const text = await kubernetesApi.getResourceYaml(clusterCode, namespace, resource, name);
setRawYaml(text);
setEditYaml(effectiveClean ? cleanYAML(text) : text);
setEditYaml(await toEditableYaml(text));
} catch (e) {
toast({ title: '获取YAML失败', description: (e as Record<string, any>).message, variant: 'destructive' });
const err = `# Error: ${e.message}`;
@@ -60,9 +67,8 @@ export function YamlViewer({ clusterCode, namespace, resource, name, onClose, on
} finally { setLoading(false); }
};
const enterEdit = () => {
const cleaned = effectiveClean ? cleanYAML(rawYaml) : rawYaml;
setEditYaml(cleaned);
const enterEdit = async () => {
setEditYaml(await toEditableYaml(rawYaml));
setEditing(true);
if (effectiveClean) {
toast({ title: '已清理只读字段', description: '已剥离 status / managedFields / uid 等' });
@@ -1,6 +1,37 @@
// K8s manifest 解析/序列化共用工具 // Logan.Li
import { cleanYAML } from './k8s-yaml-utils';
const STRIP_ANNOTATIONS = new Set([
'kubectl.kubernetes.io/last-applied-configuration',
'deployment.kubernetes.io/revision',
'kubectl.kubernetes.io/restartedAt',
]);
/** 对象级剥离运行时字段(比 cleanYAML 文本处理更可靠)// Logan.Li */
export function stripRuntimeFields(manifest: Record<string, any>): Record<string, any> {
const cloned = JSON.parse(JSON.stringify(manifest));
delete cloned.status;
if (cloned.metadata) {
delete cloned.metadata.managedFields;
delete cloned.metadata.resourceVersion;
delete cloned.metadata.uid;
delete cloned.metadata.creationTimestamp;
delete cloned.metadata.generation;
delete cloned.metadata.selfLink;
if (cloned.metadata.annotations) {
for (const k of Object.keys(cloned.metadata.annotations)) {
if (STRIP_ANNOTATIONS.has(k)) delete cloned.metadata.annotations[k];
}
if (Object.keys(cloned.metadata.annotations).length === 0) delete cloned.metadata.annotations;
}
if (cloned.metadata.labels) {
delete cloned.metadata.labels['pod-template-hash'];
if (Object.keys(cloned.metadata.labels).length === 0) delete cloned.metadata.labels;
}
}
return cloned;
}
export async function parseManifest(text: string): Promise<Record<string, any> | null> {
if (!text?.trim()) return null;
try { return JSON.parse(text); } catch {}
@@ -16,17 +47,19 @@ export async function dumpManifest(manifest: Record<string, any>): Promise<strin
return jsyaml.dump(manifest, { lineWidth: 120, noRefs: true });
}
/** 原始 YAML → 可编辑 manifest + 精简 YAML(可视化/编辑共用)// Logan.Li */
export async function parseEditableManifest(text: string): Promise<{
manifest: Record<string, any> | null;
yaml: string;
}> {
const parsed = await parseManifest(text);
if (!parsed) return { manifest: null, yaml: text };
const stripped = stripRuntimeFields(parsed);
return { manifest: stripped, yaml: await dumpManifest(stripped) };
}
/** 保存前剥离 status 等噪音字段 // Logan.Li */
export async function prepareManifestForSave(manifest: Record<string, any>): Promise<string> {
const cloned = JSON.parse(JSON.stringify(manifest));
delete cloned.status;
if (cloned.metadata) {
delete cloned.metadata.managedFields;
delete cloned.metadata.resourceVersion;
delete cloned.metadata.uid;
delete cloned.metadata.creationTimestamp;
delete cloned.metadata.generation;
}
const yaml = await dumpManifest(cloned);
const yaml = await dumpManifest(stripRuntimeFields(manifest));
return cleanYAML(yaml);
}
@@ -22,7 +22,8 @@ export function cleanYAML(yaml: string): string {
// ===== 移除配置 =====
const METADATA_LINE_KEYS = new Set([
'uid', 'resourceVersion', 'creationTimestamp', 'generation',
'selfLink', 'clusterName', 'managedFields', 'generateName',
'selfLink', 'clusterName', 'generateName',
// managedFields 走 METADATA_BLOCK_KEYS 整块删除(含同级数组项)// Logan.Li
]);
const METADATA_BLOCK_KEYS = new Set(['managedFields', 'ownerReferences']);
@@ -185,8 +186,17 @@ export function cleanYAML(yaml: string): string {
}
// metadata 块字段移除(managedFields/ownerReferences)
// K8s 数组项与 key 同级缩进(如 managedFields:\n - apiVersion),需一并跳过 // Logan.Li
if (indent === metaIndent && key && METADATA_BLOCK_KEYS.has(key)) {
skipBlock(metaIndent);
i++;
while (i < lines.length) {
const l = lines[i];
const t = l.trimStart();
if (t === '' || t.startsWith('#')) { i++; continue; }
const ind = getIndent(l);
if (ind > metaIndent || (ind === metaIndent && t.startsWith('-'))) { i++; continue; }
break;
}
continue;
}