// YamlViewer.tsx — YAML 查看/编辑器(CodeMirror 6 语法高亮) // 支持:语法高亮 + 行号 + 基本校验 + 编辑/保存 + 只读字段清理 // 作者: Logan import { useState, useEffect, useMemo, useCallback } from 'react'; import { X, Copy, Check, Save, RefreshCw, Loader2, Sparkles } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useToast } from '@/hooks/use-toast'; import { kubernetesApi } from '@/services/api'; import { cleanYAML } from './k8s-yaml-utils'; import CodeMirror from '@uiw/react-codemirror'; import { yaml } from '@codemirror/lang-yaml'; import { oneDark } from '@codemirror/theme-one-dark'; import { EditorView } from '@codemirror/view'; interface YamlViewerProps { clusterCode: string; namespace: string; resource: string; // e.g. "deployments", "services" name: string; onClose: () => void; onSaved?: () => void; /** 进入编辑模式前是否清理只读/噪音字段(managedFields/uid/status 等) */ cleanBeforeEdit?: boolean; /** 打开即自动进入编辑模式(编辑按钮专用入口) */ autoEdit?: boolean; /** 内联模式(非弹窗)— 用于 YAML 编辑模式 Tab */ inline?: boolean; /** 内联模式下 YAML 内容直接传入 */ initialYaml?: string; } export function YamlViewer({ clusterCode, namespace, resource, name, onClose, onSaved, cleanBeforeEdit = false, autoEdit = false, inline = false, initialYaml }: YamlViewerProps) { const { toast } = useToast(); const [rawYaml, setRawYaml] = useState(initialYaml || ''); // 原始(只读展示用) const [editYaml, setEditYaml] = useState(''); // 编辑态(可能被清理过) const [loading, setLoading] = useState(!initialYaml); const [saving, setSaving] = useState(false); const [editing, setEditing] = useState(autoEdit); // autoEdit → 初始即编辑态 const [copied, setCopied] = useState(false); // autoEdit 时隐式启用清理(编辑入口必须有清理,否则 status/managedFields 会被 K8s 拒绝) const effectiveClean = cleanBeforeEdit || autoEdit; const loadYaml = async () => { if (initialYaml) { setRawYaml(initialYaml); setEditYaml(effectiveClean ? cleanYAML(initialYaml) : initialYaml); setLoading(false); return; } setLoading(true); try { const text = await kubernetesApi.getResourceYaml(clusterCode, namespace, resource, name); setRawYaml(text); setEditYaml(effectiveClean ? cleanYAML(text) : text); } catch (e: any) { toast({ title: '获取YAML失败', description: e.message, variant: 'destructive' }); const err = `# Error: ${e.message}`; setRawYaml(err); setEditYaml(err); } finally { setLoading(false); } }; const enterEdit = () => { const cleaned = effectiveClean ? cleanYAML(rawYaml) : rawYaml; setEditYaml(cleaned); setEditing(true); if (effectiveClean) { toast({ title: '已清理只读字段', description: '已剥离 status / managedFields / uid 等' }); } }; // 视图模式:当 cleanBeforeEdit 时,只读视图也默认展示精简版(剥离 status/managedFields/uid/resourceVersion) // 用户可点击「原始」按钮切换回未处理的完整服务端输出 const [showRaw, setShowRaw] = useState(false); const saveYaml = async () => { setSaving(true); try { await kubernetesApi.updateResourceYaml(clusterCode, namespace, resource, name, editYaml); toast({ title: 'YAML已应用', description: `${resource}/${name} 更新成功` }); setEditing(false); setRawYaml(editYaml); onSaved?.(); } catch (e: any) { toast({ title: '保存失败', description: e.message, variant: 'destructive' }); } finally { setSaving(false); } }; const copyYaml = async () => { const content = editing ? editYaml : rawYaml; await navigator.clipboard.writeText(content); setCopied(true); setTimeout(() => setCopied(false), 2000); }; useEffect(() => { loadYaml(); }, []); // 编辑态用 editYaml;只读态根据 effectiveClean + showRaw 决定是否清理 const display = editing ? editYaml : (effectiveClean && !showRaw ? cleanYAML(rawYaml) : rawYaml); // CodeMirror 主题扩展 const extensions = useMemo(() => [ yaml(), EditorView.lineWrapping, ...(editing ? [] : [EditorView.editable.of(false)]), ], [editing]); // 内联模式 — 不显示外层弹窗,适合嵌入 Tab 页面 if (inline) { return (

{resource}/{name} · {namespace} · {clusterCode}

{effectiveClean && !editing && ( )} {!editing ? ( <> {effectiveClean && ( )} {!effectiveClean && ( )} ) : ( <> )}
{loading ? (
) : ( editing && setEditYaml(val)} basicSetup={{ lineNumbers: true, foldGutter: true, highlightActiveLine: editing, highlightActiveLineGutter: editing, autocompletion: true, bracketMatching: true, closeBrackets: true, indentOnInput: true, }} style={{ fontSize: '12px' }} /> )}
); } // 弹窗模式(原有行为) return (
e.stopPropagation()}> {/* Header */}

{resource}/{name}

{namespace} · {clusterCode}

{effectiveClean && !editing && ( )} {!editing ? ( <> {effectiveClean && ( )} {!effectiveClean && ( )} ) : ( <> )}
{/* Body */}
{loading ? (
) : ( editing && setEditYaml(val)} basicSetup={{ lineNumbers: true, foldGutter: true, highlightActiveLine: editing, highlightActiveLineGutter: editing, autocompletion: true, bracketMatching: true, closeBrackets: true, indentOnInput: true, }} style={{ fontSize: '12px' }} /> )}
); }