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:
1 parent
eeba677850
commit
fd7b04b9d6
15 files changed
+651
-150
No files matched your search
@@ -9,11 +9,12 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { kubernetesApi } from '@/api';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { createEmptyManifest } from './resource-create-templates';
|
||||
import { parseManifest, parseEditableManifest, prepareManifestForSave, prepareYamlForSave } from './k8s-manifest-utils';
|
||||
import { YamlEditorToolbar } from './YamlEditorToolbar';
|
||||
import { YamlCodeMirror } from './YamlCodeMirror';
|
||||
import { getYamlResourceConfig, yamlDownloadFilename } from './k8s-yaml-resource-config';
|
||||
import { downloadYamlFile } from '@/utils/export';
|
||||
|
||||
interface ResourceVisualEditorProps {
|
||||
clusterCode: string;
|
||||
@@ -33,23 +34,33 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
|
||||
const [rawYaml, setRawYaml] = useState('');
|
||||
const [manifest, setManifest] = useState<any>(null);
|
||||
const [mode, setMode] = useState<'visual' | 'yaml'>('visual');
|
||||
const [displayYaml, setDisplayYaml] = useState('');
|
||||
const [editYaml, setEditYaml] = useState('');
|
||||
const [yamlEditing, setYamlEditing] = useState(false);
|
||||
const [showRawYaml, setShowRawYaml] = useState(false);
|
||||
const [yamlCopied, setYamlCopied] = useState(false);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (isCreate) {
|
||||
const empty = createEmptyManifest(resource, namespace, name || '');
|
||||
const yamlStr = await prepareManifestForSave(empty);
|
||||
setManifest(empty);
|
||||
setEditYaml(JSON.stringify(empty, null, 2));
|
||||
setDisplayYaml(yamlStr);
|
||||
setEditYaml(yamlStr);
|
||||
setRawYaml('');
|
||||
setYamlEditing(true);
|
||||
return;
|
||||
}
|
||||
const text = await kubernetesApi.getResourceYaml(clusterCode, namespace, resource, name);
|
||||
setRawYaml(text);
|
||||
// 先解析原始 YAML,再用对象级剥离;cleanYAML 会破坏 managedFields 导致解析失败 // Logan.Li
|
||||
const { manifest: parsed, yaml: editableYaml } = await parseEditableManifest(text);
|
||||
setDisplayYaml(editableYaml);
|
||||
setEditYaml(editableYaml);
|
||||
setYamlEditing(false);
|
||||
setShowRawYaml(false);
|
||||
setManifest(parsed);
|
||||
if (!parsed) {
|
||||
setMode('yaml');
|
||||
@@ -94,8 +105,12 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
|
||||
toast({ title: '创建成功', description: 'YAML 已应用' });
|
||||
} else {
|
||||
await kubernetesApi.updateResourceYaml(clusterCode, namespace, resource, name, yamlStr);
|
||||
toast({ title: 'YAML 已应用', description: `${resource}/${name} 更新成功` });
|
||||
toast({ title: '保存成功', description: `${resource}/${name} 已更新` });
|
||||
}
|
||||
setYamlEditing(false);
|
||||
setDisplayYaml(yamlStr);
|
||||
setEditYaml(yamlStr);
|
||||
if (!isCreate) setRawYaml(yamlStr);
|
||||
onSaved?.();
|
||||
onClose();
|
||||
} catch (e) {
|
||||
@@ -115,9 +130,31 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
|
||||
};
|
||||
|
||||
const switchToYaml = async () => {
|
||||
if (manifest) setEditYaml(await prepareManifestForSave(manifest));
|
||||
if (manifest) {
|
||||
const yamlStr = await prepareManifestForSave(manifest);
|
||||
setDisplayYaml(yamlStr);
|
||||
setEditYaml(yamlStr);
|
||||
}
|
||||
setYamlEditing(false);
|
||||
setShowRawYaml(false);
|
||||
setMode('yaml');
|
||||
};
|
||||
|
||||
const currentYamlText = () => (yamlEditing ? editYaml : (showRawYaml ? rawYaml : displayYaml));
|
||||
|
||||
const copyYamlContent = async () => {
|
||||
await navigator.clipboard.writeText(currentYamlText());
|
||||
setYamlCopied(true);
|
||||
setTimeout(() => setYamlCopied(false), 2000);
|
||||
};
|
||||
|
||||
const downloadYamlContent = () => {
|
||||
const fname = yamlDownloadFilename(resource, namespace, name || manifest?.metadata?.name || 'resource');
|
||||
downloadYamlFile(fname, currentYamlText());
|
||||
toast({ title: '已下载', description: fname });
|
||||
};
|
||||
|
||||
const resCfg = getYamlResourceConfig(resource);
|
||||
const resourceMeta: Record<string, { title: string; icon: any; color: string }> = {
|
||||
configmaps: { title: 'ConfigMap', icon: FileText, color: 'text-blue-400' },
|
||||
secrets: { title: 'Secret', icon: Shield, color: 'text-amber-400' },
|
||||
@@ -131,7 +168,7 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
|
||||
return (
|
||||
<ModalOverlay onClose={onClose}>
|
||||
<div
|
||||
className="bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-xl w-[90vw] max-w-3xl max-h-[85vh] flex flex-col shadow-2xl"
|
||||
className="bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-xl w-[90vw] max-w-3xl h-[85vh] flex flex-col shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
@@ -161,30 +198,47 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-auto p-4">
|
||||
<div className={`flex-1 min-h-0 p-4 ${mode === 'yaml' ? 'flex flex-col overflow-hidden' : 'overflow-auto'}`}>
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-full py-20"><Loader2 className="h-6 w-6 animate-spin text-[var(--dg-text-muted)]" /></div>
|
||||
) : mode === 'yaml' ? (
|
||||
<div className="space-y-3">
|
||||
<CodeMirror
|
||||
value={editYaml}
|
||||
height="550px"
|
||||
theme={oneDark}
|
||||
extensions={[yaml()]}
|
||||
onChange={(val) => setEditYaml(val)}
|
||||
basicSetup={{ lineNumbers: true, foldGutter: true, highlightActiveLine: true, autocompletion: true }}
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm" onClick={async () => {
|
||||
const { yaml: stripped } = await parseEditableManifest(rawYaml);
|
||||
setEditYaml(stripped);
|
||||
}}>重置</Button>
|
||||
<Button size="sm" className="bg-blue-600 hover:bg-blue-700" onClick={handleSaveYaml} disabled={saving}>
|
||||
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> : <Save className="h-3.5 w-3.5 mr-1" />}
|
||||
保存
|
||||
</Button>
|
||||
<div className="flex flex-col flex-1 min-h-0 gap-3">
|
||||
<div className="flex justify-end shrink-0">
|
||||
<YamlEditorToolbar
|
||||
editing={yamlEditing}
|
||||
saving={saving}
|
||||
copied={yamlCopied}
|
||||
showRaw={showRawYaml}
|
||||
compact
|
||||
readOnly={resCfg.readOnly}
|
||||
showRawToggle={!isCreate && !!rawYaml}
|
||||
onCopy={copyYamlContent}
|
||||
onDownload={downloadYamlContent}
|
||||
onEdit={() => { setEditYaml(displayYaml); setYamlEditing(true); setShowRawYaml(false); }}
|
||||
onCancel={async () => {
|
||||
setYamlEditing(false);
|
||||
if (isCreate) {
|
||||
const empty = createEmptyManifest(resource, namespace, name || '');
|
||||
const yamlStr = await prepareManifestForSave(empty);
|
||||
setDisplayYaml(yamlStr);
|
||||
setEditYaml(yamlStr);
|
||||
return;
|
||||
}
|
||||
const { yaml: stripped } = await parseEditableManifest(rawYaml);
|
||||
setDisplayYaml(stripped);
|
||||
setEditYaml(stripped);
|
||||
}}
|
||||
onSave={handleSaveYaml}
|
||||
onToggleRaw={() => setShowRawYaml((v) => !v)}
|
||||
/>
|
||||
</div>
|
||||
<YamlCodeMirror
|
||||
value={yamlEditing ? editYaml : (showRawYaml ? rawYaml : displayYaml)}
|
||||
editing={yamlEditing}
|
||||
fill
|
||||
fontSize="12px"
|
||||
onChange={(val) => yamlEditing && setEditYaml(val)}
|
||||
/>
|
||||
</div>
|
||||
) : manifest ? (
|
||||
<>
|
||||
|
||||
Reference in new issue
Block a user