refactor(quality): Q21 前端巨型组件拆分 — DeploymentEditAdvanced 590L→289L (-51%), 提取 types(parseAdvancedManifest+buildAdvancedManifest 纯函数) + UpdateStrategyCard/TolerationsCard/PodMiscCard/PodSecurityCard/HostSettingsCard 5 个受控 Card 组件, manifest 解析(106L)和序列化(121L)提取为纯函数, 零逻辑变更, ESLint errors=0/warnings=65/any=0, tsc -b=0, npm build✓, 17/17 测试通过
This commit is contained in:
1 parent
a530d3898c
commit
b4591de97c
10 files changed
+833
-430
No files matched your search
@@ -1,19 +1,24 @@
|
||||
// DeploymentEditAdvanced.tsx — 编辑器「高级设置」Tab (M8) // Logan.Li
|
||||
// 编辑内容:更新策略 / 历史版本 / 调度(节点选择/容忍) / DNS / 安全上下文 / 终止宽限期 / 调度器 / SA
|
||||
// Q21 拆分: 提取 types(parser+builder) + UpdateStrategyCard + TolerationsCard + PodMiscCard + PodSecurityCard + HostSettingsCard
|
||||
// 作者: Logan <admin@attacker.club>
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Save, Loader2, Settings, RefreshCw, Shield, Clock, Network, Plus, Trash2,
|
||||
} from 'lucide-react';
|
||||
import { Loader2, Save } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { kubernetesApi } from '@/api';
|
||||
import { saveDeploymentManifest } from './deployment-manifest-save';
|
||||
import { SchedulingSection, type ScheduleMode } from './deployment-edit-advanced/SchedulingSection';
|
||||
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';
|
||||
|
||||
@@ -26,14 +31,6 @@ interface Props {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
interface Toleration {
|
||||
key: string;
|
||||
operator: string; // Equal | Exists
|
||||
value: string;
|
||||
effect: string; // NoSchedule | PreferNoSchedule | NoExecute
|
||||
tolerationSeconds: string;
|
||||
}
|
||||
|
||||
export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName, manifest, onSaved, onCancel }: Props) {
|
||||
const { toast } = useToast();
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -50,13 +47,15 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
const [progressDeadlineSeconds, setProgressDeadlineSeconds] = useState('600');
|
||||
|
||||
// ──── 调度策略 ────
|
||||
const [scheduleMode, setScheduleMode] = useState<ScheduleMode>('nodeSelector');
|
||||
const [scheduleMode, setScheduleMode] = useState<AdvancedState['scheduleMode']>('nodeSelector');
|
||||
const [nodeName, setNodeName] = useState('');
|
||||
const [availableNodes, setAvailableNodes] = useState<Array<{ name: string; ip: string; labels: Record<string, string> }>>([]);
|
||||
const [availableNodes, setAvailableNodes] = useState<NodeInfo[]>([]);
|
||||
const [nodesLoading, setNodesLoading] = useState(false);
|
||||
const [labelSelectorKv, setLabelSelectorKv] = useState<Array<{ key: string; value: string }>>([]);
|
||||
const [nodeLabels, setNodeLabels] = useState<Record<string, string[]>>({});
|
||||
const [tolerations, setTolerations] = useState<Toleration[]>([]);
|
||||
|
||||
// Pod 级别杂项
|
||||
const [dnsPolicy, setDnsPolicy] = useState('ClusterFirst');
|
||||
const [restartPolicy, setRestartPolicy] = useState('Always');
|
||||
const [schedulerName, setSchedulerName] = useState('default-scheduler');
|
||||
@@ -69,61 +68,57 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
const [podRunAsGroup, setPodRunAsGroup] = useState('');
|
||||
const [podRunAsNonRoot, setPodRunAsNonRoot] = useState(false);
|
||||
|
||||
// 主机设置(M8 补全)
|
||||
// 主机设置
|
||||
const [hostNetwork, setHostNetwork] = useState(false);
|
||||
const [hostIPC, setHostIPC] = useState(false);
|
||||
const [hostPID, setHostPID] = useState(false);
|
||||
const [shareProcessNamespace, setShareProcessNamespace] = useState(false);
|
||||
|
||||
// hostAliases(M8 补全)
|
||||
// hostAliases
|
||||
const [hostAliases, setHostAliases] = useState<Array<{ ip: string; hostnames: string }>>([]);
|
||||
|
||||
// dnsConfig(M8 补全)
|
||||
// dnsConfig
|
||||
const [dnsNameservers, setDnsNameservers] = useState('');
|
||||
const [dnsSearches, setDnsSearches] = useState('');
|
||||
const [dnsOptions, setDnsOptions] = useState<Array<{ name: string; value: string }>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const m = manifest;
|
||||
if (!m) { setLoading(false); return; }
|
||||
setRawManifest(m);
|
||||
const spec = m?.spec || {};
|
||||
const podSpec = spec?.template?.spec || {};
|
||||
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);
|
||||
|
||||
// 更新策略
|
||||
const strat = spec.strategy || {};
|
||||
setStrategyType(strat.type || 'RollingUpdate');
|
||||
const ru = strat.rollingUpdate || {};
|
||||
setMaxSurge(String(ru.maxSurge ?? '25%'));
|
||||
setMaxUnavailable(String(ru.maxUnavailable ?? '25%'));
|
||||
|
||||
// 历史/进度
|
||||
setRevisionHistoryLimit(spec.revisionHistoryLimit != null ? String(spec.revisionHistoryLimit) : '10');
|
||||
setProgressDeadlineSeconds(spec.progressDeadlineSeconds != null ? String(spec.progressDeadlineSeconds) : '600');
|
||||
|
||||
// nodeSelector → labelSelectorKv
|
||||
const ns = podSpec.nodeSelector || {};
|
||||
const nsEntries = Object.entries(ns).map(([k, v]) => ({ key: k, value: String(v) }));
|
||||
setLabelSelectorKv(nsEntries);
|
||||
|
||||
// nodeName
|
||||
setNodeName(podSpec.nodeName || '');
|
||||
|
||||
// Determine scheduleMode
|
||||
if (podSpec.nodeName) {
|
||||
setScheduleMode('nodeName');
|
||||
} else if (podSpec.affinity?.nodeAffinity) {
|
||||
setScheduleMode('nodeAffinity');
|
||||
} else if (nsEntries.length > 0) {
|
||||
setScheduleMode('nodeSelector');
|
||||
}
|
||||
|
||||
// 加载节点列表
|
||||
// 加载节点列表(副作用,留在 useEffect 内)
|
||||
if (clusterCode) {
|
||||
setNodesLoading(true);
|
||||
kubernetesApi.getClusterNodes(clusterCode).then((res: JsonData) => {
|
||||
const nodes: JsonData[] = Array.isArray(res) ? res : (res?.items || res?.list || []);
|
||||
const mapped = nodes.map((n: JsonData) => {
|
||||
const mapped: NodeInfo[] = nodes.map((n: JsonData) => {
|
||||
const meta = n.metadata || {};
|
||||
const status = n.status || {};
|
||||
const addresses: JsonData[] = Array.isArray(status.addresses) ? status.addresses : [];
|
||||
@@ -143,50 +138,6 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
setNodeLabels(lblMap);
|
||||
}).catch(() => {}).finally(() => setNodesLoading(false));
|
||||
}
|
||||
|
||||
// tolerations
|
||||
const tols: JsonData[] = Array.isArray(podSpec.tolerations) ? podSpec.tolerations : [];
|
||||
setTolerations(tols.map(t => ({
|
||||
key: t.key || '',
|
||||
operator: t.operator || 'Equal',
|
||||
value: t.value || '',
|
||||
effect: t.effect || '',
|
||||
tolerationSeconds: t.tolerationSeconds != null ? String(t.tolerationSeconds) : '',
|
||||
})));
|
||||
|
||||
setDnsPolicy(podSpec.dnsPolicy || 'ClusterFirst');
|
||||
setRestartPolicy(podSpec.restartPolicy || 'Always');
|
||||
setSchedulerName(podSpec.schedulerName || 'default-scheduler');
|
||||
setTerminationGracePeriodSeconds(podSpec.terminationGracePeriodSeconds != null ? String(podSpec.terminationGracePeriodSeconds) : '30');
|
||||
setServiceAccountName(podSpec.serviceAccountName || '');
|
||||
|
||||
// Pod securityContext
|
||||
const sc = podSpec.securityContext || {};
|
||||
setPodFsGroup(sc.fsGroup != null ? String(sc.fsGroup) : '');
|
||||
setPodRunAsUser(sc.runAsUser != null ? String(sc.runAsUser) : '');
|
||||
setPodRunAsGroup(sc.runAsGroup != null ? String(sc.runAsGroup) : '');
|
||||
setPodRunAsNonRoot(!!sc.runAsNonRoot);
|
||||
|
||||
// 主机设置(M8 补全)
|
||||
setHostNetwork(!!podSpec.hostNetwork);
|
||||
setHostIPC(!!podSpec.hostIPC);
|
||||
setHostPID(!!podSpec.hostPID);
|
||||
setShareProcessNamespace(!!podSpec.shareProcessNamespace);
|
||||
|
||||
// hostAliases(M8 补全)
|
||||
const ha: JsonData[] = Array.isArray(podSpec.hostAliases) ? podSpec.hostAliases : [];
|
||||
setHostAliases(ha.map(a => ({
|
||||
ip: a.ip || '',
|
||||
hostnames: Array.isArray(a.hostnames) ? a.hostnames.join(', ') : '',
|
||||
})));
|
||||
|
||||
// dnsConfig(M8 补全)
|
||||
const dc = podSpec.dnsConfig || {};
|
||||
setDnsNameservers(Array.isArray(dc.nameservers) ? dc.nameservers.join('\n') : '');
|
||||
setDnsSearches(Array.isArray(dc.searches) ? dc.searches.join('\n') : '');
|
||||
const opts: JsonData[] = Array.isArray(dc.options) ? dc.options : [];
|
||||
setDnsOptions(opts.map(o => ({ name: o.name || '', value: o.value || '' })));
|
||||
|
||||
setLoading(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [manifest]);
|
||||
@@ -197,252 +148,73 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
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<Toleration>) =>
|
||||
setTolerations(prev => prev.map((t, idx) => (idx === i ? { ...t, ...patch } : t)));
|
||||
|
||||
const save = async () => {
|
||||
setSaving(true);
|
||||
try {
|
||||
const base = JSON.parse(JSON.stringify(rawManifest));
|
||||
base.spec = base.spec || {};
|
||||
base.spec.template = base.spec.template || {};
|
||||
base.spec.template.spec = base.spec.template.spec || {};
|
||||
const podSpec = base.spec.template.spec;
|
||||
|
||||
// 更新策略
|
||||
base.spec.strategy = { type: strategyType };
|
||||
if (strategyType === 'RollingUpdate') {
|
||||
base.spec.strategy.rollingUpdate = { maxSurge, maxUnavailable };
|
||||
}
|
||||
|
||||
// 历史/进度
|
||||
if (revisionHistoryLimit) base.spec.revisionHistoryLimit = Number(revisionHistoryLimit);
|
||||
if (progressDeadlineSeconds) base.spec.progressDeadlineSeconds = Number(progressDeadlineSeconds);
|
||||
|
||||
// nodeSelector / nodeName / nodeAffinity
|
||||
if (scheduleMode === 'nodeName' && nodeName.trim()) {
|
||||
podSpec.nodeName = nodeName.trim();
|
||||
delete podSpec.nodeSelector;
|
||||
if (podSpec.affinity) delete podSpec.affinity.nodeAffinity;
|
||||
} else if (scheduleMode === 'nodeSelector') {
|
||||
const nsObj: Record<string, string> = {};
|
||||
for (const { key, value } of labelSelectorKv) {
|
||||
const k = key.trim();
|
||||
if (k) nsObj[k] = value;
|
||||
}
|
||||
if (Object.keys(nsObj).length > 0) podSpec.nodeSelector = nsObj;
|
||||
else delete podSpec.nodeSelector;
|
||||
if (podSpec.nodeName) delete podSpec.nodeName;
|
||||
if (podSpec.affinity) delete podSpec.affinity.nodeAffinity;
|
||||
} else if (scheduleMode === 'nodeAffinity') {
|
||||
const matchExpressions: JsonData[] = labelSelectorKv
|
||||
.filter(s => s.key.trim())
|
||||
.map(s => ({ key: s.key.trim(), operator: 'In', values: [s.value] }));
|
||||
if (matchExpressions.length > 0) {
|
||||
podSpec.affinity = podSpec.affinity || {};
|
||||
podSpec.affinity.nodeAffinity = {
|
||||
requiredDuringSchedulingIgnoredDuringExecution: {
|
||||
nodeSelectorTerms: [{ matchExpressions }],
|
||||
},
|
||||
};
|
||||
} else if (podSpec.affinity) {
|
||||
delete podSpec.affinity.nodeAffinity;
|
||||
}
|
||||
delete podSpec.nodeSelector;
|
||||
if (podSpec.nodeName) delete podSpec.nodeName;
|
||||
}
|
||||
|
||||
// tolerations
|
||||
const tolsK8s = tolerations
|
||||
.filter(t => t.key || t.operator === 'Exists')
|
||||
.map(t => {
|
||||
const out: JsonData = {};
|
||||
if (t.key) out.key = t.key;
|
||||
if (t.operator) out.operator = t.operator;
|
||||
if (t.value && t.operator !== 'Exists') out.value = t.value;
|
||||
if (t.effect) out.effect = t.effect;
|
||||
if (t.tolerationSeconds && t.effect === 'NoExecute') out.tolerationSeconds = Number(t.tolerationSeconds);
|
||||
return out;
|
||||
});
|
||||
if (tolsK8s.length > 0) podSpec.tolerations = tolsK8s;
|
||||
else delete podSpec.tolerations;
|
||||
|
||||
// 其他 Pod 级别
|
||||
if (dnsPolicy) podSpec.dnsPolicy = dnsPolicy;
|
||||
if (restartPolicy) podSpec.restartPolicy = restartPolicy;
|
||||
if (schedulerName) podSpec.schedulerName = schedulerName;
|
||||
if (terminationGracePeriodSeconds) podSpec.terminationGracePeriodSeconds = Number(terminationGracePeriodSeconds);
|
||||
if (serviceAccountName) podSpec.serviceAccountName = serviceAccountName;
|
||||
|
||||
// Pod 安全上下文
|
||||
const sc: JsonData = {};
|
||||
if (podFsGroup) sc.fsGroup = Number(podFsGroup);
|
||||
if (podRunAsUser) sc.runAsUser = Number(podRunAsUser);
|
||||
if (podRunAsGroup) sc.runAsGroup = Number(podRunAsGroup);
|
||||
if (podRunAsNonRoot) sc.runAsNonRoot = true;
|
||||
if (Object.keys(sc).length > 0) podSpec.securityContext = sc;
|
||||
else delete podSpec.securityContext;
|
||||
|
||||
// 主机设置(M8 补全)— 只在为 true 时写入,false 删除字段
|
||||
if (hostNetwork) podSpec.hostNetwork = true; else delete podSpec.hostNetwork;
|
||||
if (hostIPC) podSpec.hostIPC = true; else delete podSpec.hostIPC;
|
||||
if (hostPID) podSpec.hostPID = true; else delete podSpec.hostPID;
|
||||
if (shareProcessNamespace) podSpec.shareProcessNamespace = true; else delete podSpec.shareProcessNamespace;
|
||||
|
||||
// hostAliases(M8 补全)
|
||||
const haK8s = hostAliases
|
||||
.filter(a => a.ip.trim())
|
||||
.map(a => ({
|
||||
ip: a.ip.trim(),
|
||||
hostnames: a.hostnames.split(/[,\s]+/).map((s: string) => s.trim()).filter(Boolean),
|
||||
}))
|
||||
.filter(a => a.hostnames.length > 0);
|
||||
if (haK8s.length > 0) podSpec.hostAliases = haK8s;
|
||||
else delete podSpec.hostAliases;
|
||||
|
||||
// dnsConfig(M8 补全)— 仅 dnsPolicy=None 时 dnsConfig 才有意义,但仍允许编辑
|
||||
const dcObj: JsonData = {};
|
||||
const ns = dnsNameservers.split('\n').map(s => s.trim()).filter(Boolean);
|
||||
if (ns.length > 0) dcObj.nameservers = ns;
|
||||
const sr = dnsSearches.split('\n').map(s => s.trim()).filter(Boolean);
|
||||
if (sr.length > 0) dcObj.searches = sr;
|
||||
const dkOpts = dnsOptions.filter(o => o.name.trim()).map(o => {
|
||||
const out: JsonData = { name: o.name.trim() };
|
||||
if (o.value.trim()) out.value = o.value.trim();
|
||||
return out;
|
||||
});
|
||||
if (dkOpts.length > 0) dcObj.options = dkOpts;
|
||||
if (Object.keys(dcObj).length > 0) podSpec.dnsConfig = dcObj;
|
||||
else delete podSpec.dnsConfig;
|
||||
|
||||
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?.message || String(e), variant: 'destructive' });
|
||||
toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: e instanceof Error ? e.message : String(e), variant: 'destructive' });
|
||||
} finally { setSaving(false); }
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-12 text-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin mx-auto dg-text-muted" />
|
||||
<p className="dg-text-muted mt-3 text-sm"> {i18n.t('monitor.logPanel.logResults.loading')} </p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="p-12 text-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin mx-auto dg-text-muted" />
|
||||
<p className="dg-text-muted mt-3 text-sm"> {i18n.t('monitor.logPanel.logResults.loading')} </p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!manifest) {
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-8 text-center dg-text-muted">
|
||||
{i18n.t('k8s.editAdvanced.noManifest')}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="p-8 text-center dg-text-muted">
|
||||
{i18n.t('k8s.editAdvanced.noManifest')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 更新策略 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<RefreshCw className="h-4 w-4 dg-text-blue" />{i18n.t('k8s.editAdvanced.updateStrategy')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="flex items-center gap-1.5 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="radio" checked={strategyType === 'RollingUpdate'} onChange={() => setStrategyType('RollingUpdate')}
|
||||
className="accent-blue-500" />
|
||||
RollingUpdate(滚动更新)
|
||||
</label>
|
||||
<label className="flex items-center gap-1.5 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="radio" checked={strategyType === 'Recreate'} onChange={() => setStrategyType('Recreate')}
|
||||
className="accent-blue-500" />
|
||||
Recreate(重建)
|
||||
</label>
|
||||
</div>
|
||||
{strategyType === 'RollingUpdate' && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">maxSurge(整数或百分比,超出期望副本数)</Label>
|
||||
<Input value={maxSurge} onChange={e => setMaxSurge(e.target.value)}
|
||||
placeholder="25%" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">maxUnavailable(整数或百分比,不可用上限)</Label>
|
||||
<Input value={maxUnavailable} onChange={e => setMaxUnavailable(e.target.value)}
|
||||
placeholder="25%" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<UpdateStrategyCard
|
||||
strategyType={strategyType}
|
||||
onStrategyTypeChange={setStrategyType}
|
||||
maxSurge={maxSurge}
|
||||
onMaxSurgeChange={setMaxSurge}
|
||||
maxUnavailable={maxUnavailable}
|
||||
onMaxUnavailableChange={setMaxUnavailable}
|
||||
revisionHistoryLimit={revisionHistoryLimit}
|
||||
onRevisionHistoryLimitChange={setRevisionHistoryLimit}
|
||||
progressDeadlineSeconds={progressDeadlineSeconds}
|
||||
onProgressDeadlineSecondsChange={setProgressDeadlineSeconds}
|
||||
/>
|
||||
|
||||
{/* 历史版本 / 进度 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Clock className="h-4 w-4 dg-text-yellow" />历史版本 & 进度
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">revisionHistoryLimit(保留 RS 历史)</Label>
|
||||
<Input type="number" value={revisionHistoryLimit} onChange={e => setRevisionHistoryLimit(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">progressDeadlineSeconds(升级超时秒数)</Label>
|
||||
<Input type="number" value={progressDeadlineSeconds} onChange={e => setProgressDeadlineSeconds(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 容忍 (Tolerations) — 此前仅有状态无 UI // Logan.Li */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)]"> {i18n.t('kubernetes.deploymentedita.s13a8310b')} </CardTitle>
|
||||
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs"
|
||||
onClick={() => setTolerations(prev => [...prev, { key: '', operator: 'Equal', value: '', effect: 'NoSchedule', tolerationSeconds: '' }])}>
|
||||
<Plus className="h-3 w-3 mr-1" />添加
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-2">
|
||||
{tolerations.length === 0 && <p className="text-xs dg-text-muted"> {i18n.t('kubernetes.deploymentedita.s8bc06bde')} </p>}
|
||||
{tolerations.map((t, i) => (
|
||||
<div key={i} className="grid grid-cols-[1fr_80px_1fr_100px_80px_auto] gap-2 items-center">
|
||||
<Input value={t.key} placeholder="key" onChange={e => setTolerations(prev => prev.map((x, idx) => idx === i ? { ...x, key: e.target.value } : x))}
|
||||
className="h-7 dg-input text-xs font-mono" />
|
||||
<select value={t.operator} onChange={e => setTolerations(prev => prev.map((x, idx) => idx === i ? { ...x, operator: e.target.value } : x))}
|
||||
className="h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1">
|
||||
<option value="Equal">Equal</option>
|
||||
<option value="Exists">Exists</option>
|
||||
</select>
|
||||
<Input value={t.value} placeholder="value" disabled={t.operator === 'Exists'}
|
||||
onChange={e => setTolerations(prev => prev.map((x, idx) => idx === i ? { ...x, value: e.target.value } : x))}
|
||||
className="h-7 dg-input text-xs font-mono" />
|
||||
<select value={t.effect} onChange={e => setTolerations(prev => prev.map((x, idx) => idx === i ? { ...x, effect: e.target.value } : x))}
|
||||
className="h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1">
|
||||
<option value=""> {i18n.t('kubernetes.deploymentedita.s2a6eb2dd')} </option>
|
||||
<option value="NoSchedule">NoSchedule</option>
|
||||
<option value="PreferNoSchedule">PreferNoSchedule</option>
|
||||
<option value="NoExecute">NoExecute</option>
|
||||
</select>
|
||||
<Input value={t.tolerationSeconds} placeholder={i18n.t('k8s.editAdvanced.s0c1fec65')} disabled={t.effect !== 'NoExecute'}
|
||||
onChange={e => setTolerations(prev => prev.map((x, idx) => idx === i ? { ...x, tolerationSeconds: e.target.value } : x))}
|
||||
className="h-7 dg-input text-xs font-mono" />
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-red" onClick={() => setTolerations(prev => prev.filter((_, idx) => idx !== i))}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TolerationsCard
|
||||
tolerations={tolerations}
|
||||
onAdd={addToleration}
|
||||
onRemove={removeToleration}
|
||||
onUpdate={updateToleration}
|
||||
/>
|
||||
|
||||
<SchedulingSection
|
||||
scheduleMode={scheduleMode}
|
||||
@@ -458,114 +230,40 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
nodeLabels={nodeLabels}
|
||||
/>
|
||||
|
||||
{/* Pod 级别杂项 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Settings className="h-4 w-4 dg-text-secondary" />Pod 级别
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">dnsPolicy</Label>
|
||||
<select value={dnsPolicy} onChange={e => setDnsPolicy(e.target.value)}
|
||||
className="h-8 mt-1 w-full bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-2">
|
||||
<option value="ClusterFirst">ClusterFirst</option>
|
||||
<option value="ClusterFirstWithHostNet">ClusterFirstWithHostNet</option>
|
||||
<option value="Default">Default</option>
|
||||
<option value="None">None</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">restartPolicy</Label>
|
||||
<select value={restartPolicy} onChange={e => setRestartPolicy(e.target.value)}
|
||||
className="h-8 mt-1 w-full bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-2">
|
||||
<option value="Always">Always</option>
|
||||
<option value="OnFailure">OnFailure</option>
|
||||
<option value="Never">Never</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">schedulerName</Label>
|
||||
<Input value={schedulerName} onChange={e => setSchedulerName(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">terminationGracePeriodSeconds</Label>
|
||||
<Input type="number" value={terminationGracePeriodSeconds} onChange={e => setTerminationGracePeriodSeconds(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label className="dg-text-muted text-xs">serviceAccountName</Label>
|
||||
<Input value={serviceAccountName} onChange={e => setServiceAccountName(e.target.value)}
|
||||
placeholder="default" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<PodMiscCard
|
||||
dnsPolicy={dnsPolicy}
|
||||
onDnsPolicyChange={setDnsPolicy}
|
||||
restartPolicy={restartPolicy}
|
||||
onRestartPolicyChange={setRestartPolicy}
|
||||
schedulerName={schedulerName}
|
||||
onSchedulerNameChange={setSchedulerName}
|
||||
terminationGracePeriodSeconds={terminationGracePeriodSeconds}
|
||||
onTerminationGracePeriodSecondsChange={setTerminationGracePeriodSeconds}
|
||||
serviceAccountName={serviceAccountName}
|
||||
onServiceAccountNameChange={setServiceAccountName}
|
||||
/>
|
||||
|
||||
{/* Pod 安全上下文 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Shield className="h-4 w-4 text-rose-400" />Pod 安全上下文 (securityContext)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">fsGroup</Label>
|
||||
<Input type="number" value={podFsGroup} onChange={e => setPodFsGroup(e.target.value)}
|
||||
placeholder="1000" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">runAsUser</Label>
|
||||
<Input type="number" value={podRunAsUser} onChange={e => setPodRunAsUser(e.target.value)}
|
||||
placeholder="1000" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">runAsGroup</Label>
|
||||
<Input type="number" value={podRunAsGroup} onChange={e => setPodRunAsGroup(e.target.value)}
|
||||
placeholder="1000" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div className="flex items-end pb-1">
|
||||
<label className="flex items-center gap-2 text-xs dg-text-secondary cursor-pointer">
|
||||
<input type="checkbox" checked={podRunAsNonRoot} onChange={e => setPodRunAsNonRoot(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
runAsNonRoot
|
||||
</label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<PodSecurityCard
|
||||
podFsGroup={podFsGroup}
|
||||
onPodFsGroupChange={setPodFsGroup}
|
||||
podRunAsUser={podRunAsUser}
|
||||
onPodRunAsUserChange={setPodRunAsUser}
|
||||
podRunAsGroup={podRunAsGroup}
|
||||
onPodRunAsGroupChange={setPodRunAsGroup}
|
||||
podRunAsNonRoot={podRunAsNonRoot}
|
||||
onPodRunAsNonRootChange={setPodRunAsNonRoot}
|
||||
/>
|
||||
|
||||
{/* 主机设置(M8 补全) */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Network className="h-4 w-4 dg-text-cyan" />主机设置 (Host Namespace)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={hostNetwork} onChange={e => setHostNetwork(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
hostNetwork(共享主机网络命名空间)
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={hostIPC} onChange={e => setHostIPC(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
hostIPC(共享主机 IPC 命名空间)
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={hostPID} onChange={e => setHostPID(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
hostPID(共享主机 PID 命名空间)
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={shareProcessNamespace} onChange={e => setShareProcessNamespace(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
shareProcessNamespace(共享进程命名空间)
|
||||
</label>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<HostSettingsCard
|
||||
hostNetwork={hostNetwork}
|
||||
onHostNetworkChange={setHostNetwork}
|
||||
hostIPC={hostIPC}
|
||||
onHostIPCChange={setHostIPC}
|
||||
hostPID={hostPID}
|
||||
onHostPIDChange={setHostPID}
|
||||
shareProcessNamespace={shareProcessNamespace}
|
||||
onShareProcessNamespaceChange={setShareProcessNamespace}
|
||||
/>
|
||||
|
||||
<DnsHostSection
|
||||
dnsNameservers={dnsNameservers}
|
||||
@@ -588,4 +286,4 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// HostSettingsCard.tsx — 主机设置 (Host Namespace) 卡片 (Q21 提取)
|
||||
// hostNetwork / hostIPC / hostPID / shareProcessNamespace
|
||||
import { Network } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
interface HostSettingsCardProps {
|
||||
hostNetwork: boolean;
|
||||
onHostNetworkChange: (v: boolean) => void;
|
||||
hostIPC: boolean;
|
||||
onHostIPCChange: (v: boolean) => void;
|
||||
hostPID: boolean;
|
||||
onHostPIDChange: (v: boolean) => void;
|
||||
shareProcessNamespace: boolean;
|
||||
onShareProcessNamespaceChange: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export function HostSettingsCard({
|
||||
hostNetwork, onHostNetworkChange,
|
||||
hostIPC, onHostIPCChange,
|
||||
hostPID, onHostPIDChange,
|
||||
shareProcessNamespace, onShareProcessNamespaceChange,
|
||||
}: HostSettingsCardProps) {
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Network className="h-4 w-4 dg-text-cyan" />主机设置 (Host Namespace)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={hostNetwork} onChange={e => onHostNetworkChange(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
hostNetwork(共享主机网络命名空间)
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={hostIPC} onChange={e => onHostIPCChange(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
hostIPC(共享主机 IPC 命名空间)
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={hostPID} onChange={e => onHostPIDChange(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
hostPID(共享主机 PID 命名空间)
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="checkbox" checked={shareProcessNamespace} onChange={e => onShareProcessNamespaceChange(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
shareProcessNamespace(共享进程命名空间)
|
||||
</label>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// PodMiscCard.tsx — Pod 级别杂项设置卡片 (Q21 提取)
|
||||
// dnsPolicy / restartPolicy / schedulerName / terminationGracePeriodSeconds / serviceAccountName
|
||||
import { Settings } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
||||
interface PodMiscCardProps {
|
||||
dnsPolicy: string;
|
||||
onDnsPolicyChange: (v: string) => void;
|
||||
restartPolicy: string;
|
||||
onRestartPolicyChange: (v: string) => void;
|
||||
schedulerName: string;
|
||||
onSchedulerNameChange: (v: string) => void;
|
||||
terminationGracePeriodSeconds: string;
|
||||
onTerminationGracePeriodSecondsChange: (v: string) => void;
|
||||
serviceAccountName: string;
|
||||
onServiceAccountNameChange: (v: string) => void;
|
||||
}
|
||||
|
||||
export function PodMiscCard({
|
||||
dnsPolicy, onDnsPolicyChange,
|
||||
restartPolicy, onRestartPolicyChange,
|
||||
schedulerName, onSchedulerNameChange,
|
||||
terminationGracePeriodSeconds, onTerminationGracePeriodSecondsChange,
|
||||
serviceAccountName, onServiceAccountNameChange,
|
||||
}: PodMiscCardProps) {
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Settings className="h-4 w-4 dg-text-secondary" />Pod 级别
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">dnsPolicy</Label>
|
||||
<select value={dnsPolicy} onChange={e => onDnsPolicyChange(e.target.value)}
|
||||
className="h-8 mt-1 w-full bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-2">
|
||||
<option value="ClusterFirst">ClusterFirst</option>
|
||||
<option value="ClusterFirstWithHostNet">ClusterFirstWithHostNet</option>
|
||||
<option value="Default">Default</option>
|
||||
<option value="None">None</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">restartPolicy</Label>
|
||||
<select value={restartPolicy} onChange={e => onRestartPolicyChange(e.target.value)}
|
||||
className="h-8 mt-1 w-full bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-2">
|
||||
<option value="Always">Always</option>
|
||||
<option value="OnFailure">OnFailure</option>
|
||||
<option value="Never">Never</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">schedulerName</Label>
|
||||
<Input value={schedulerName} onChange={e => onSchedulerNameChange(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">terminationGracePeriodSeconds</Label>
|
||||
<Input type="number" value={terminationGracePeriodSeconds} onChange={e => onTerminationGracePeriodSecondsChange(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label className="dg-text-muted text-xs">serviceAccountName</Label>
|
||||
<Input value={serviceAccountName} onChange={e => onServiceAccountNameChange(e.target.value)}
|
||||
placeholder="default" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// PodSecurityCard.tsx — Pod 安全上下文 (securityContext) 卡片 (Q21 提取)
|
||||
import { Shield } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
||||
interface PodSecurityCardProps {
|
||||
podFsGroup: string;
|
||||
onPodFsGroupChange: (v: string) => void;
|
||||
podRunAsUser: string;
|
||||
onPodRunAsUserChange: (v: string) => void;
|
||||
podRunAsGroup: string;
|
||||
onPodRunAsGroupChange: (v: string) => void;
|
||||
podRunAsNonRoot: boolean;
|
||||
onPodRunAsNonRootChange: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export function PodSecurityCard({
|
||||
podFsGroup, onPodFsGroupChange,
|
||||
podRunAsUser, onPodRunAsUserChange,
|
||||
podRunAsGroup, onPodRunAsGroupChange,
|
||||
podRunAsNonRoot, onPodRunAsNonRootChange,
|
||||
}: PodSecurityCardProps) {
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Shield className="h-4 w-4 text-rose-400" />Pod 安全上下文 (securityContext)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">fsGroup</Label>
|
||||
<Input type="number" value={podFsGroup} onChange={e => onPodFsGroupChange(e.target.value)}
|
||||
placeholder="1000" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">runAsUser</Label>
|
||||
<Input type="number" value={podRunAsUser} onChange={e => onPodRunAsUserChange(e.target.value)}
|
||||
placeholder="1000" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">runAsGroup</Label>
|
||||
<Input type="number" value={podRunAsGroup} onChange={e => onPodRunAsGroupChange(e.target.value)}
|
||||
placeholder="1000" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div className="flex items-end pb-1">
|
||||
<label className="flex items-center gap-2 text-xs dg-text-secondary cursor-pointer">
|
||||
<input type="checkbox" checked={podRunAsNonRoot} onChange={e => onPodRunAsNonRootChange(e.target.checked)}
|
||||
className="accent-blue-500" />
|
||||
runAsNonRoot
|
||||
</label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// TolerationsCard.tsx — 容忍 (Tolerations) CRUD 卡片 (Q21 提取)
|
||||
// 受控展示组件,接收 tolerations 数组 + 增删改回调。
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import i18n from '@/i18n';
|
||||
import type { Toleration } from './types';
|
||||
|
||||
interface TolerationsCardProps {
|
||||
tolerations: Toleration[];
|
||||
onAdd: () => void;
|
||||
onRemove: (index: number) => void;
|
||||
onUpdate: (index: number, patch: Partial<Toleration>) => void;
|
||||
}
|
||||
|
||||
export function TolerationsCard({ tolerations, onAdd, onRemove, onUpdate }: TolerationsCardProps) {
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)]"> {i18n.t('kubernetes.deploymentedita.s13a8310b')} </CardTitle>
|
||||
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs"
|
||||
onClick={onAdd}>
|
||||
<Plus className="h-3 w-3 mr-1" />添加
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-2">
|
||||
{tolerations.length === 0 && <p className="text-xs dg-text-muted"> {i18n.t('kubernetes.deploymentedita.s8bc06bde')} </p>}
|
||||
{tolerations.map((t, i) => (
|
||||
<div key={i} className="grid grid-cols-[1fr_80px_1fr_100px_80px_auto] gap-2 items-center">
|
||||
<Input value={t.key} placeholder="key" onChange={e => onUpdate(i, { key: e.target.value })}
|
||||
className="h-7 dg-input text-xs font-mono" />
|
||||
<select value={t.operator} onChange={e => onUpdate(i, { operator: e.target.value })}
|
||||
className="h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1">
|
||||
<option value="Equal">Equal</option>
|
||||
<option value="Exists">Exists</option>
|
||||
</select>
|
||||
<Input value={t.value} placeholder="value" disabled={t.operator === 'Exists'}
|
||||
onChange={e => onUpdate(i, { value: e.target.value })}
|
||||
className="h-7 dg-input text-xs font-mono" />
|
||||
<select value={t.effect} onChange={e => onUpdate(i, { effect: e.target.value })}
|
||||
className="h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1">
|
||||
<option value=""> {i18n.t('kubernetes.deploymentedita.s2a6eb2dd')} </option>
|
||||
<option value="NoSchedule">NoSchedule</option>
|
||||
<option value="PreferNoSchedule">PreferNoSchedule</option>
|
||||
<option value="NoExecute">NoExecute</option>
|
||||
</select>
|
||||
<Input value={t.tolerationSeconds} placeholder={i18n.t('k8s.editAdvanced.s0c1fec65')} disabled={t.effect !== 'NoExecute'}
|
||||
onChange={e => onUpdate(i, { tolerationSeconds: e.target.value })}
|
||||
className="h-7 dg-input text-xs font-mono" />
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-red" onClick={() => onRemove(i)}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// UpdateStrategyCard.tsx — 更新策略 + 历史版本/进度 卡片 (Q21 提取)
|
||||
// 受控展示组件,接收 state 片段 + setter 回调。
|
||||
import { RefreshCw, Clock } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
interface UpdateStrategyCardProps {
|
||||
strategyType: 'RollingUpdate' | 'Recreate';
|
||||
onStrategyTypeChange: (t: 'RollingUpdate' | 'Recreate') => void;
|
||||
maxSurge: string;
|
||||
onMaxSurgeChange: (v: string) => void;
|
||||
maxUnavailable: string;
|
||||
onMaxUnavailableChange: (v: string) => void;
|
||||
revisionHistoryLimit: string;
|
||||
onRevisionHistoryLimitChange: (v: string) => void;
|
||||
progressDeadlineSeconds: string;
|
||||
onProgressDeadlineSecondsChange: (v: string) => void;
|
||||
}
|
||||
|
||||
export function UpdateStrategyCard({
|
||||
strategyType, onStrategyTypeChange,
|
||||
maxSurge, onMaxSurgeChange,
|
||||
maxUnavailable, onMaxUnavailableChange,
|
||||
revisionHistoryLimit, onRevisionHistoryLimitChange,
|
||||
progressDeadlineSeconds, onProgressDeadlineSecondsChange,
|
||||
}: UpdateStrategyCardProps) {
|
||||
return (
|
||||
<>
|
||||
{/* 更新策略 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<RefreshCw className="h-4 w-4 dg-text-blue" />{i18n.t('k8s.editAdvanced.updateStrategy')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="flex items-center gap-1.5 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="radio" checked={strategyType === 'RollingUpdate'} onChange={() => onStrategyTypeChange('RollingUpdate')}
|
||||
className="accent-blue-500" />
|
||||
RollingUpdate(滚动更新)
|
||||
</label>
|
||||
<label className="flex items-center gap-1.5 text-xs text-[var(--dg-text-secondary)] cursor-pointer">
|
||||
<input type="radio" checked={strategyType === 'Recreate'} onChange={() => onStrategyTypeChange('Recreate')}
|
||||
className="accent-blue-500" />
|
||||
Recreate(重建)
|
||||
</label>
|
||||
</div>
|
||||
{strategyType === 'RollingUpdate' && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">maxSurge(整数或百分比,超出期望副本数)</Label>
|
||||
<Input value={maxSurge} onChange={e => onMaxSurgeChange(e.target.value)}
|
||||
placeholder="25%" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">maxUnavailable(整数或百分比,不可用上限)</Label>
|
||||
<Input value={maxUnavailable} onChange={e => onMaxUnavailableChange(e.target.value)}
|
||||
placeholder="25%" className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 历史版本 / 进度 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Clock className="h-4 w-4 dg-text-yellow" />历史版本 & 进度
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">revisionHistoryLimit(保留 RS 历史)</Label>
|
||||
<Input type="number" value={revisionHistoryLimit} onChange={e => onRevisionHistoryLimitChange(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">progressDeadlineSeconds(升级超时秒数)</Label>
|
||||
<Input type="number" value={progressDeadlineSeconds} onChange={e => onProgressDeadlineSecondsChange(e.target.value)}
|
||||
className="h-8 mt-1 dg-input text-sm font-mono" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// types.ts — DeploymentEditAdvanced 共享类型 + 状态聚合接口
|
||||
// 提取自 DeploymentEditAdvanced.tsx (Q21 拆分),纯数据/类型定义,零 React 依赖。
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// 从 SchedulingSection 复用调度相关类型,保持单一来源
|
||||
export type { ScheduleMode, LabelSelectorEntry, ClusterNodeOption } from './SchedulingSection';
|
||||
|
||||
export interface Toleration {
|
||||
key: string;
|
||||
operator: string; // Equal | Exists
|
||||
value: string;
|
||||
effect: string; // NoSchedule | PreferNoSchedule | NoExecute
|
||||
tolerationSeconds: string;
|
||||
}
|
||||
|
||||
export interface HostAlias {
|
||||
ip: string;
|
||||
hostnames: string;
|
||||
}
|
||||
|
||||
export interface DnsOption {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface NodeInfo {
|
||||
name: string;
|
||||
ip: string;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合 DeploymentEditAdvanced 的全部高级设置状态。
|
||||
* 由 parseAdvancedManifest() 从 manifest 解析填充,
|
||||
* 由 buildAdvancedManifest() 序列化回 manifest。
|
||||
*/
|
||||
export interface AdvancedState {
|
||||
// 更新策略
|
||||
strategyType: 'RollingUpdate' | 'Recreate';
|
||||
maxSurge: string;
|
||||
maxUnavailable: string;
|
||||
// 历史 / 进度
|
||||
revisionHistoryLimit: string;
|
||||
progressDeadlineSeconds: string;
|
||||
// 调度
|
||||
scheduleMode: 'nodeName' | 'nodeSelector' | 'nodeAffinity';
|
||||
nodeName: string;
|
||||
labelSelectorKv: Array<{ key: string; value: string }>;
|
||||
tolerations: Toleration[];
|
||||
// Pod 级别杂项
|
||||
dnsPolicy: string;
|
||||
restartPolicy: string;
|
||||
schedulerName: string;
|
||||
terminationGracePeriodSeconds: string;
|
||||
serviceAccountName: string;
|
||||
// Pod 安全上下文
|
||||
podFsGroup: string;
|
||||
podRunAsUser: string;
|
||||
podRunAsGroup: string;
|
||||
podRunAsNonRoot: boolean;
|
||||
// 主机命名空间
|
||||
hostNetwork: boolean;
|
||||
hostIPC: boolean;
|
||||
hostPID: boolean;
|
||||
shareProcessNamespace: boolean;
|
||||
// hostAliases
|
||||
hostAliases: HostAlias[];
|
||||
// dnsConfig
|
||||
dnsNameservers: string;
|
||||
dnsSearches: string;
|
||||
dnsOptions: DnsOption[];
|
||||
}
|
||||
|
||||
/** 从 manifest JSON 中提取 AdvancedState(纯函数,无副作用)。 */
|
||||
export function parseAdvancedManifest(m: JsonData | null): AdvancedState {
|
||||
const empty: AdvancedState = {
|
||||
strategyType: 'RollingUpdate', maxSurge: '25%', maxUnavailable: '25%',
|
||||
revisionHistoryLimit: '10', progressDeadlineSeconds: '600',
|
||||
scheduleMode: 'nodeSelector', nodeName: '', labelSelectorKv: [], tolerations: [],
|
||||
dnsPolicy: 'ClusterFirst', restartPolicy: 'Always', schedulerName: 'default-scheduler',
|
||||
terminationGracePeriodSeconds: '30', serviceAccountName: '',
|
||||
podFsGroup: '', podRunAsUser: '', podRunAsGroup: '', podRunAsNonRoot: false,
|
||||
hostNetwork: false, hostIPC: false, hostPID: false, shareProcessNamespace: false,
|
||||
hostAliases: [], dnsNameservers: '', dnsSearches: '', dnsOptions: [],
|
||||
};
|
||||
if (!m) return empty;
|
||||
|
||||
const spec: JsonData = (m as JsonData).spec || {};
|
||||
const podSpec: JsonData = spec?.template?.spec || {};
|
||||
|
||||
// 更新策略
|
||||
const strat: JsonData = spec.strategy || {};
|
||||
const ru: JsonData = strat.rollingUpdate || {};
|
||||
const strategyType = (strat.type === 'Recreate' ? 'Recreate' : 'RollingUpdate') as 'RollingUpdate' | 'Recreate';
|
||||
|
||||
// nodeSelector → labelSelectorKv
|
||||
const ns: JsonData = podSpec.nodeSelector || {};
|
||||
const nsEntries = Object.entries(ns).map(([k, v]) => ({ key: k, value: String(v) }));
|
||||
|
||||
// Determine scheduleMode
|
||||
let scheduleMode: AdvancedState['scheduleMode'] = 'nodeSelector';
|
||||
if (podSpec.nodeName) {
|
||||
scheduleMode = 'nodeName';
|
||||
} else if (podSpec.affinity?.nodeAffinity) {
|
||||
scheduleMode = 'nodeAffinity';
|
||||
} else if (nsEntries.length > 0) {
|
||||
scheduleMode = 'nodeSelector';
|
||||
}
|
||||
|
||||
// tolerations
|
||||
const tols: JsonData[] = Array.isArray(podSpec.tolerations) ? podSpec.tolerations : [];
|
||||
const tolerations: Toleration[] = tols.map((t: JsonData) => ({
|
||||
key: t.key || '',
|
||||
operator: t.operator || 'Equal',
|
||||
value: t.value || '',
|
||||
effect: t.effect || '',
|
||||
tolerationSeconds: t.tolerationSeconds != null ? String(t.tolerationSeconds) : '',
|
||||
}));
|
||||
|
||||
// Pod securityContext
|
||||
const sc: JsonData = podSpec.securityContext || {};
|
||||
|
||||
// hostAliases
|
||||
const ha: JsonData[] = Array.isArray(podSpec.hostAliases) ? podSpec.hostAliases : [];
|
||||
const hostAliases: HostAlias[] = ha.map((a: JsonData) => ({
|
||||
ip: a.ip || '',
|
||||
hostnames: Array.isArray(a.hostnames) ? a.hostnames.join(', ') : '',
|
||||
}));
|
||||
|
||||
// dnsConfig
|
||||
const dc: JsonData = podSpec.dnsConfig || {};
|
||||
const opts: JsonData[] = Array.isArray(dc.options) ? dc.options : [];
|
||||
|
||||
return {
|
||||
strategyType,
|
||||
maxSurge: String(ru.maxSurge ?? '25%'),
|
||||
maxUnavailable: String(ru.maxUnavailable ?? '25%'),
|
||||
revisionHistoryLimit: spec.revisionHistoryLimit != null ? String(spec.revisionHistoryLimit) : '10',
|
||||
progressDeadlineSeconds: spec.progressDeadlineSeconds != null ? String(spec.progressDeadlineSeconds) : '600',
|
||||
scheduleMode,
|
||||
nodeName: podSpec.nodeName || '',
|
||||
labelSelectorKv: nsEntries,
|
||||
tolerations,
|
||||
dnsPolicy: podSpec.dnsPolicy || 'ClusterFirst',
|
||||
restartPolicy: podSpec.restartPolicy || 'Always',
|
||||
schedulerName: podSpec.schedulerName || 'default-scheduler',
|
||||
terminationGracePeriodSeconds: podSpec.terminationGracePeriodSeconds != null ? String(podSpec.terminationGracePeriodSeconds) : '30',
|
||||
serviceAccountName: podSpec.serviceAccountName || '',
|
||||
podFsGroup: sc.fsGroup != null ? String(sc.fsGroup) : '',
|
||||
podRunAsUser: sc.runAsUser != null ? String(sc.runAsUser) : '',
|
||||
podRunAsGroup: sc.runAsGroup != null ? String(sc.runAsGroup) : '',
|
||||
podRunAsNonRoot: !!sc.runAsNonRoot,
|
||||
hostNetwork: !!podSpec.hostNetwork,
|
||||
hostIPC: !!podSpec.hostIPC,
|
||||
hostPID: !!podSpec.hostPID,
|
||||
shareProcessNamespace: !!podSpec.shareProcessNamespace,
|
||||
hostAliases,
|
||||
dnsNameservers: Array.isArray(dc.nameservers) ? dc.nameservers.join('\n') : '',
|
||||
dnsSearches: Array.isArray(dc.searches) ? dc.searches.join('\n') : '',
|
||||
dnsOptions: opts.map((o: JsonData) => ({ name: o.name || '', value: o.value || '' })),
|
||||
};
|
||||
}
|
||||
|
||||
/** 将 AdvancedState 序列化回 manifest JSON(纯函数,返回深拷贝后的新 manifest)。 */
|
||||
export function buildAdvancedManifest(rawManifest: JsonData, s: AdvancedState): JsonData {
|
||||
const base = JSON.parse(JSON.stringify(rawManifest));
|
||||
base.spec = base.spec || {};
|
||||
base.spec.template = base.spec.template || {};
|
||||
base.spec.template.spec = base.spec.template.spec || {};
|
||||
const podSpec: JsonData = base.spec.template.spec;
|
||||
|
||||
// 更新策略
|
||||
base.spec.strategy = { type: s.strategyType };
|
||||
if (s.strategyType === 'RollingUpdate') {
|
||||
base.spec.strategy.rollingUpdate = { maxSurge: s.maxSurge, maxUnavailable: s.maxUnavailable };
|
||||
}
|
||||
|
||||
// 历史 / 进度
|
||||
if (s.revisionHistoryLimit) base.spec.revisionHistoryLimit = Number(s.revisionHistoryLimit);
|
||||
if (s.progressDeadlineSeconds) base.spec.progressDeadlineSeconds = Number(s.progressDeadlineSeconds);
|
||||
|
||||
// nodeSelector / nodeName / nodeAffinity
|
||||
if (s.scheduleMode === 'nodeName' && s.nodeName.trim()) {
|
||||
podSpec.nodeName = s.nodeName.trim();
|
||||
delete podSpec.nodeSelector;
|
||||
if (podSpec.affinity) delete podSpec.affinity.nodeAffinity;
|
||||
} else if (s.scheduleMode === 'nodeSelector') {
|
||||
const nsObj: Record<string, string> = {};
|
||||
for (const { key, value } of s.labelSelectorKv) {
|
||||
const k = key.trim();
|
||||
if (k) nsObj[k] = value;
|
||||
}
|
||||
if (Object.keys(nsObj).length > 0) podSpec.nodeSelector = nsObj;
|
||||
else delete podSpec.nodeSelector;
|
||||
if (podSpec.nodeName) delete podSpec.nodeName;
|
||||
if (podSpec.affinity) delete podSpec.affinity.nodeAffinity;
|
||||
} else if (s.scheduleMode === 'nodeAffinity') {
|
||||
const matchExpressions: JsonData[] = s.labelSelectorKv
|
||||
.filter((item) => item.key.trim())
|
||||
.map((item) => ({ key: item.key.trim(), operator: 'In', values: [item.value] }));
|
||||
if (matchExpressions.length > 0) {
|
||||
podSpec.affinity = podSpec.affinity || {};
|
||||
podSpec.affinity.nodeAffinity = {
|
||||
requiredDuringSchedulingIgnoredDuringExecution: {
|
||||
nodeSelectorTerms: [{ matchExpressions }],
|
||||
},
|
||||
};
|
||||
} else if (podSpec.affinity) {
|
||||
delete podSpec.affinity.nodeAffinity;
|
||||
}
|
||||
delete podSpec.nodeSelector;
|
||||
if (podSpec.nodeName) delete podSpec.nodeName;
|
||||
}
|
||||
|
||||
// tolerations
|
||||
const tolsK8s = s.tolerations
|
||||
.filter((t) => t.key || t.operator === 'Exists')
|
||||
.map((t) => {
|
||||
const out: JsonData = {};
|
||||
if (t.key) out.key = t.key;
|
||||
if (t.operator) out.operator = t.operator;
|
||||
if (t.value && t.operator !== 'Exists') out.value = t.value;
|
||||
if (t.effect) out.effect = t.effect;
|
||||
if (t.tolerationSeconds && t.effect === 'NoExecute') out.tolerationSeconds = Number(t.tolerationSeconds);
|
||||
return out;
|
||||
});
|
||||
if (tolsK8s.length > 0) podSpec.tolerations = tolsK8s;
|
||||
else delete podSpec.tolerations;
|
||||
|
||||
// 其他 Pod 级别
|
||||
if (s.dnsPolicy) podSpec.dnsPolicy = s.dnsPolicy;
|
||||
if (s.restartPolicy) podSpec.restartPolicy = s.restartPolicy;
|
||||
if (s.schedulerName) podSpec.schedulerName = s.schedulerName;
|
||||
if (s.terminationGracePeriodSeconds) podSpec.terminationGracePeriodSeconds = Number(s.terminationGracePeriodSeconds);
|
||||
if (s.serviceAccountName) podSpec.serviceAccountName = s.serviceAccountName;
|
||||
|
||||
// Pod 安全上下文
|
||||
const sc: JsonData = {};
|
||||
if (s.podFsGroup) sc.fsGroup = Number(s.podFsGroup);
|
||||
if (s.podRunAsUser) sc.runAsUser = Number(s.podRunAsUser);
|
||||
if (s.podRunAsGroup) sc.runAsGroup = Number(s.podRunAsGroup);
|
||||
if (s.podRunAsNonRoot) sc.runAsNonRoot = true;
|
||||
if (Object.keys(sc).length > 0) podSpec.securityContext = sc;
|
||||
else delete podSpec.securityContext;
|
||||
|
||||
// 主机设置 — 只在为 true 时写入,false 删除字段
|
||||
if (s.hostNetwork) podSpec.hostNetwork = true; else delete podSpec.hostNetwork;
|
||||
if (s.hostIPC) podSpec.hostIPC = true; else delete podSpec.hostIPC;
|
||||
if (s.hostPID) podSpec.hostPID = true; else delete podSpec.hostPID;
|
||||
if (s.shareProcessNamespace) podSpec.shareProcessNamespace = true; else delete podSpec.shareProcessNamespace;
|
||||
|
||||
// hostAliases
|
||||
const haK8s = s.hostAliases
|
||||
.filter((a) => a.ip.trim())
|
||||
.map((a) => ({
|
||||
ip: a.ip.trim(),
|
||||
hostnames: a.hostnames.split(/[,\s]+/).map((str: string) => str.trim()).filter(Boolean),
|
||||
}))
|
||||
.filter((a) => a.hostnames.length > 0);
|
||||
if (haK8s.length > 0) podSpec.hostAliases = haK8s;
|
||||
else delete podSpec.hostAliases;
|
||||
|
||||
// dnsConfig
|
||||
const dcObj: JsonData = {};
|
||||
const nsArr = s.dnsNameservers.split('\n').map((str: string) => str.trim()).filter(Boolean);
|
||||
if (nsArr.length > 0) dcObj.nameservers = nsArr;
|
||||
const srArr = s.dnsSearches.split('\n').map((str: string) => str.trim()).filter(Boolean);
|
||||
if (srArr.length > 0) dcObj.searches = srArr;
|
||||
const dkOpts = s.dnsOptions.filter((o) => o.name.trim()).map((o) => {
|
||||
const out: JsonData = { name: o.name.trim() };
|
||||
if (o.value.trim()) out.value = o.value.trim();
|
||||
return out;
|
||||
});
|
||||
if (dkOpts.length > 0) dcObj.options = dkOpts;
|
||||
if (Object.keys(dcObj).length > 0) podSpec.dnsConfig = dcObj;
|
||||
else delete podSpec.dnsConfig;
|
||||
|
||||
return base;
|
||||
}
|
||||
Reference in new issue
Block a user