// DeploymentEditAdvanced.tsx — 编辑器「高级设置」Tab (M8) // Logan.Li // 编辑内容:更新策略 / 历史版本 / 调度(节点选择/容忍) / DNS / 安全上下文 / 终止宽限期 / 调度器 / SA // Q21 拆分: 提取 types(parser+builder) + UpdateStrategyCard + TolerationsCard + PodMiscCard + PodSecurityCard + HostSettingsCard // 作者: Logan import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Loader2, Save } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import { kubernetesApi } from '@/api'; import { saveDeploymentManifest } from './deployment-manifest-save'; import { SchedulingSection } from './deployment-edit-advanced/SchedulingSection'; import { DnsHostSection } from './deployment-edit-advanced/DnsHostSection'; import { parseAdvancedManifest, buildAdvancedManifest, type AdvancedState, type Toleration, type NodeInfo, } from './deployment-edit-advanced/types'; import { UpdateStrategyCard } from './deployment-edit-advanced/UpdateStrategyCard'; import { TolerationsCard } from './deployment-edit-advanced/TolerationsCard'; import { PodMiscCard } from './deployment-edit-advanced/PodMiscCard'; import { PodSecurityCard } from './deployment-edit-advanced/PodSecurityCard'; import { HostSettingsCard } from './deployment-edit-advanced/HostSettingsCard'; import type { JsonData } from '@/types/common'; import i18n from '@/i18n'; interface Props { clusterCode: string; namespace: string; deploymentName: string; manifest: JsonData | null; onSaved: (updated?: JsonData) => void; onCancel: () => void; } export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName, manifest, onSaved, onCancel }: Props) { const { toast } = useToast(); const [saving, setSaving] = useState(false); const [loading, setLoading] = useState(!manifest); const [rawManifest, setRawManifest] = useState(manifest || {}); // 更新策略 const [strategyType, setStrategyType] = useState<'RollingUpdate' | 'Recreate'>('RollingUpdate'); const [maxSurge, setMaxSurge] = useState('25%'); const [maxUnavailable, setMaxUnavailable] = useState('25%'); // 历史/进度 const [revisionHistoryLimit, setRevisionHistoryLimit] = useState('10'); const [progressDeadlineSeconds, setProgressDeadlineSeconds] = useState('600'); // ──── 调度策略 ──── const [scheduleMode, setScheduleMode] = useState('nodeSelector'); const [nodeName, setNodeName] = useState(''); const [availableNodes, setAvailableNodes] = useState([]); const [nodesLoading, setNodesLoading] = useState(false); const [labelSelectorKv, setLabelSelectorKv] = useState>([]); const [nodeLabels, setNodeLabels] = useState>({}); const [tolerations, setTolerations] = useState([]); // Pod 级别杂项 const [dnsPolicy, setDnsPolicy] = useState('ClusterFirst'); const [restartPolicy, setRestartPolicy] = useState('Always'); const [schedulerName, setSchedulerName] = useState('default-scheduler'); const [terminationGracePeriodSeconds, setTerminationGracePeriodSeconds] = useState('30'); const [serviceAccountName, setServiceAccountName] = useState(''); // Pod 安全上下文 const [podFsGroup, setPodFsGroup] = useState(''); const [podRunAsUser, setPodRunAsUser] = useState(''); const [podRunAsGroup, setPodRunAsGroup] = useState(''); const [podRunAsNonRoot, setPodRunAsNonRoot] = useState(false); // 主机设置 const [hostNetwork, setHostNetwork] = useState(false); const [hostIPC, setHostIPC] = useState(false); const [hostPID, setHostPID] = useState(false); const [shareProcessNamespace, setShareProcessNamespace] = useState(false); // hostAliases const [hostAliases, setHostAliases] = useState>([]); // dnsConfig const [dnsNameservers, setDnsNameservers] = useState(''); const [dnsSearches, setDnsSearches] = useState(''); const [dnsOptions, setDnsOptions] = useState>([]); useEffect(() => { if (!manifest) { setLoading(false); return; } setRawManifest(manifest); const s = parseAdvancedManifest(manifest); setStrategyType(s.strategyType); setMaxSurge(s.maxSurge); setMaxUnavailable(s.maxUnavailable); setRevisionHistoryLimit(s.revisionHistoryLimit); setProgressDeadlineSeconds(s.progressDeadlineSeconds); setScheduleMode(s.scheduleMode); setNodeName(s.nodeName); setLabelSelectorKv(s.labelSelectorKv); setTolerations(s.tolerations); setDnsPolicy(s.dnsPolicy); setRestartPolicy(s.restartPolicy); setSchedulerName(s.schedulerName); setTerminationGracePeriodSeconds(s.terminationGracePeriodSeconds); setServiceAccountName(s.serviceAccountName); setPodFsGroup(s.podFsGroup); setPodRunAsUser(s.podRunAsUser); setPodRunAsGroup(s.podRunAsGroup); setPodRunAsNonRoot(s.podRunAsNonRoot); setHostNetwork(s.hostNetwork); setHostIPC(s.hostIPC); setHostPID(s.hostPID); setShareProcessNamespace(s.shareProcessNamespace); setHostAliases(s.hostAliases); setDnsNameservers(s.dnsNameservers); setDnsSearches(s.dnsSearches); setDnsOptions(s.dnsOptions); // 加载节点列表(副作用,留在 useEffect 内) if (clusterCode) { setNodesLoading(true); kubernetesApi.getClusterNodes(clusterCode).then((res: JsonData) => { const nodes: JsonData[] = Array.isArray(res) ? res : (res?.items || res?.list || []); const mapped: NodeInfo[] = nodes.map((n: JsonData) => { const meta = n.metadata || {}; const status = n.status || {}; const addresses: JsonData[] = Array.isArray(status.addresses) ? status.addresses : []; const internalIP = addresses.find((a: JsonData) => a.type === 'InternalIP')?.address || ''; return { name: meta.name || '', ip: internalIP, labels: meta.labels || {} }; }); setAvailableNodes(mapped); // 聚合标签 const lblMap: Record = {}; for (const n of mapped) { for (const [k, v] of Object.entries(n.labels)) { const sv = String(v); if (!lblMap[k]) lblMap[k] = []; if (!lblMap[k].includes(sv)) lblMap[k].push(sv); } } setNodeLabels(lblMap); }).catch(() => {}).finally(() => setNodesLoading(false)); } setLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [manifest]); // labelSelector 操作 const addNs = () => setLabelSelectorKv(prev => [...prev, { key: '', value: '' }]); const removeNs = (i: number) => setLabelSelectorKv(prev => prev.filter((_, idx) => idx !== i)); const updateNs = (i: number, patch: Partial<{ key: string; value: string }>) => setLabelSelectorKv(prev => prev.map((n, idx) => (idx === i ? { ...n, ...patch } : n))); // tolerations 操作 const addToleration = () => setTolerations(prev => [...prev, { key: '', operator: 'Equal', value: '', effect: 'NoSchedule', tolerationSeconds: '' }]); const removeToleration = (i: number) => setTolerations(prev => prev.filter((_, idx) => idx !== i)); const updateToleration = (i: number, patch: Partial) => setTolerations(prev => prev.map((t, idx) => (idx === i ? { ...t, ...patch } : t))); const save = async () => { setSaving(true); try { const state: AdvancedState = { strategyType, maxSurge, maxUnavailable, revisionHistoryLimit, progressDeadlineSeconds, scheduleMode, nodeName, labelSelectorKv, tolerations, dnsPolicy, restartPolicy, schedulerName, terminationGracePeriodSeconds, serviceAccountName, podFsGroup, podRunAsUser, podRunAsGroup, podRunAsNonRoot, hostNetwork, hostIPC, hostPID, shareProcessNamespace, hostAliases, dnsNameservers, dnsSearches, dnsOptions, }; const base = buildAdvancedManifest(rawManifest, state); await saveDeploymentManifest(clusterCode, namespace, deploymentName, base); toast({ title: i18n.t('kubernetes.deploymentedita.s9762d0a4'), description: `${deploymentName} 更新已提交` }); onSaved(base); } catch (e) { toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: e instanceof Error ? e.message : String(e), variant: 'destructive' }); } finally { setSaving(false); } }; if (loading) { return (

{i18n.t('monitor.logPanel.logResults.loading')}

); } if (!manifest) { return (
{i18n.t('k8s.editAdvanced.noManifest')}
); } return (
{/* 操作栏 */}
); }