使用 i18n.t() 全局方式(避免 hook 定位问题): - 288 目标文件中 135 个成功转换 - 2228 处中文替换为 i18n.t() 调用 - 1006 个新翻译 key 添加到 zh-CN.json + en-US.json - 词典总量: 3554 keys (22 namespaces) - tsc 零错误 + Vite 构建成功 - 6 个含代码示例/数据字符串的文件安全回退
486 lines
22 KiB
TypeScript
486 lines
22 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||
// DeploymentWorkload.tsx — Kuboard 风格 Deployment 详情主组件
|
||
// - 顶部操作菜单: 编辑 | 预览/编辑YAML | 调整镜像版本 | 伸缩 | 日志/终端 | 重启 | 暂停/恢复更新 | CI/CD发布 | 删除 | 元数据 | 运行时 | 服务 | 应用路由
|
||
// - 三模式: view(只读增强看板)/ edit(可视化编辑器)/ yaml(YAML编辑器)
|
||
// 作者: Logan.Li
|
||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { Card, CardContent } from '@/components/ui/card';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import {
|
||
ArrowLeft,
|
||
Image as ImageIcon,
|
||
RotateCw,
|
||
Trash2,
|
||
RefreshCw,
|
||
XCircle,
|
||
CheckCircle,
|
||
AlertTriangle,
|
||
Pause,
|
||
Play,
|
||
Rocket,
|
||
} from 'lucide-react';
|
||
import { kubernetesApi } from '@/api';
|
||
import { useToast } from '@/hooks/use-toast';
|
||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||
import { useUserStore } from '@/store';
|
||
import { YamlViewer } from './YamlViewer';
|
||
import { loadDeploymentBundleYaml, saveDeploymentBundleYaml } from './deployment-bundle-yaml';
|
||
import { DeploymentView } from './DeploymentView';
|
||
import { PodExecTerminal } from './PodExecTerminal';
|
||
import { PodFileBrowser } from './PodFileBrowser';
|
||
import { EditModeContainer } from './deployment-workload/EditModeContainer';
|
||
import { CICDWebhookContent } from './deployment-workload/CICDWebhookContent';
|
||
import { ModalShell } from './deployment-workload/ModalShell';
|
||
import { PodLogsModal } from './deployment-workload/PodLogsModal';
|
||
import { ActionModals } from './deployment-workload/ActionModals';
|
||
import { createEmptyManifest } from './resource-create-templates';
|
||
import { CREATE_DEPLOYMENT_SLUG, isCreateDeployment } from './deployment-constants';
|
||
import { extractContainers } from './deployment-detail/helpers';
|
||
import { PageShell } from '@/components/layout/PageShell';
|
||
import type { ActionModal, DeploymentWorkloadProps } from './deployment-workload/types';
|
||
import i18n from '@/i18n';
|
||
|
||
export type { DeploymentWorkloadProps, WorkloadMode } from './deployment-workload/types';
|
||
|
||
export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mode, onBack, onModeChange }: DeploymentWorkloadProps) {
|
||
const isCreate = isCreateDeployment(deploymentName);
|
||
const navigate = useNavigate();
|
||
const { toast } = useToast();
|
||
const confirm = useConfirm();
|
||
const [deployment, setDeployment] = useState<any>(null);
|
||
const [manifest, setManifest] = useState<any>(null);
|
||
const [pods, setPods] = useState<any[]>([]);
|
||
const [replicaSets, setReplicaSets] = useState<any[]>([]);
|
||
const [revisions, setRevisions] = useState<any[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [actionLoading, setActionLoading] = useState('');
|
||
const [modal, setModal] = useState<ActionModal>(null);
|
||
// 调整镜像版本
|
||
const [newImage, setNewImage] = useState('');
|
||
// 伸缩
|
||
const [scaleValue, setScaleValue] = useState(0);
|
||
const [terminalPod, setTerminalPod] = useState<string | null>(null);
|
||
const [fileBrowserPod, setFileBrowserPod] = useState<string | null>(null);
|
||
const clusterId = deployment?.cluster_id || deployment?.clusterId || clusterCode;
|
||
|
||
// CI/CD curl 示例:镜像与主容器名与当前 Deployment 对齐 // Logan.Li
|
||
const cicdContainerName = useMemo(() => {
|
||
const fromManifest = manifest?.spec?.template?.spec?.containers?.[0]?.name;
|
||
if (fromManifest) return fromManifest as string;
|
||
if (pods.length > 0) {
|
||
const names = extractContainers(pods[0]);
|
||
if (names.length) return names[0];
|
||
}
|
||
return '';
|
||
}, [manifest, pods]);
|
||
|
||
const toArray = ( v: any) => (Array.isArray(v) ? v : (v?.list || v?.items || v?.data || []));
|
||
|
||
const loadData = useCallback(async () => {
|
||
if (isCreate) {
|
||
const empty = createEmptyManifest('deployments', namespace, 'my-app');
|
||
setDeployment({ name: 'my-app', namespace, replicas: 1 });
|
||
setManifest(empty);
|
||
setPods([]);
|
||
setReplicaSets([]);
|
||
setRevisions([]);
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
setLoading(true);
|
||
try {
|
||
const [dep, podRes, rsRes, revRes] = await Promise.all([
|
||
kubernetesApi.getDeployment(clusterCode, namespace, deploymentName).catch(() => null),
|
||
kubernetesApi.getNamespacePods(clusterCode, namespace).catch(() => []),
|
||
kubernetesApi.getReplicaSets(clusterCode, namespace).catch(() => []),
|
||
kubernetesApi.getDeploymentRevisions(clusterCode, namespace, deploymentName).catch(() => []),
|
||
]);
|
||
const depData = dep || (Array.isArray(dep) ? dep[0] : null);
|
||
setDeployment(depData);
|
||
setScaleValue(depData?.replicas || 0);
|
||
setNewImage(depData?.image || '');
|
||
|
||
const allPods = toArray(podRes);
|
||
// 过滤当前 Deployment 的 Pod(按名称前缀,兼容 ownerReference 不可用场景)
|
||
const depPods = allPods.filter(( p: any) =>
|
||
p.namespace === namespace && (p.name || '').startsWith(deploymentName)
|
||
);
|
||
setPods(depPods);
|
||
|
||
const allRs = toArray(rsRes);
|
||
const depRs = allRs.filter(( rs: any) =>
|
||
(rs.namespace === namespace) &&
|
||
((rs.owner_deployment === deploymentName) || (rs.name || '').startsWith(deploymentName))
|
||
);
|
||
setReplicaSets(depRs);
|
||
|
||
setRevisions(toArray(revRes));
|
||
} catch (e) {
|
||
console.error(i18n.t('kubernetes.deploymentdetai.sb46ea9c4'), e);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [clusterCode, namespace, deploymentName, isCreate]);
|
||
|
||
// 加载 manifest(用于 edit 模式 + YAML 编辑)
|
||
const loadManifest = useCallback(async () => {
|
||
if (isCreate) return;
|
||
try {
|
||
const yamlText = await kubernetesApi.getResourceYaml(clusterCode, namespace, 'deployments', deploymentName);
|
||
const { parseEditableManifest } = await import('./k8s-manifest-utils');
|
||
const { manifest: parsed } = await parseEditableManifest(yamlText);
|
||
setManifest(parsed || deployment);
|
||
} catch (e) {
|
||
console.warn(i18n.t('kubernetes.deploymentworkl.sce123fbd'), e);
|
||
setManifest(deployment);
|
||
}
|
||
}, [clusterCode, namespace, deploymentName, deployment]);
|
||
|
||
const loadBundleYaml = useCallback(
|
||
() => loadDeploymentBundleYaml(clusterCode, namespace, deploymentName),
|
||
[clusterCode, namespace, deploymentName],
|
||
);
|
||
|
||
const saveBundleYaml = useCallback(
|
||
(yaml: string) => saveDeploymentBundleYaml(clusterCode, namespace, yaml),
|
||
[clusterCode, namespace],
|
||
);
|
||
|
||
useEffect(() => { loadData(); }, [loadData]);
|
||
useEffect(() => {
|
||
if (isCreate || mode !== 'edit' || manifest) return;
|
||
loadManifest();
|
||
}, [mode, manifest, loadManifest, isCreate]);
|
||
|
||
// ============ 操作 ============
|
||
const _handleScale = async (delta: number) => {
|
||
const newReplicas = (deployment?.replicas || 0) + delta;
|
||
if (newReplicas < 0) return;
|
||
setActionLoading('scale');
|
||
try {
|
||
await kubernetesApi.scaleDeployment(clusterCode, namespace, deploymentName, newReplicas);
|
||
toast({ title: i18n.t('k8s.workloads.scaleSuccess'), description: `副本数已设置为 ${newReplicas}` });
|
||
loadData();
|
||
} catch (e) {
|
||
toast({ title: i18n.t('cloud.operationFailed'), description: e?.message || String(e), variant: 'destructive' });
|
||
} finally { setActionLoading(''); }
|
||
};
|
||
|
||
const handleScaleTo = async () => {
|
||
setActionLoading('scale');
|
||
try {
|
||
await kubernetesApi.scaleDeployment(clusterCode, namespace, deploymentName, scaleValue);
|
||
toast({ title: i18n.t('kubernetes.msg.scaleSuccess'), description: `副本数已设置为 ${scaleValue}` });
|
||
setModal(null);
|
||
loadData();
|
||
} catch (e) {
|
||
toast({ title: i18n.t('cloud.operationFailed'), description: e?.message || String(e), variant: 'destructive' });
|
||
} finally { setActionLoading(''); }
|
||
};
|
||
|
||
const handleRestart = async () => {
|
||
const ok = await confirm({
|
||
title: i18n.t('kubernetes.deployment.restart'),
|
||
description: `确认滚动重启 Deployment "${deploymentName}"?`,
|
||
confirmText: i18n.t('agent.agentServiceRestart'),
|
||
});
|
||
if (!ok) return;
|
||
setActionLoading('restart');
|
||
try {
|
||
await kubernetesApi.restartDeployment(clusterCode, namespace, deploymentName);
|
||
toast({ title: i18n.t('kubernetes.deploymentdetai.s68a70009'), description: i18n.t('kubernetes.deploymentdetai.sd187617a') });
|
||
setTimeout(loadData, 1500);
|
||
} catch (e) {
|
||
toast({ title: i18n.t('kubernetes.deployment.restartFailed'), description: e?.message || String(e), variant: 'destructive' });
|
||
} finally { setActionLoading(''); }
|
||
};
|
||
|
||
const handleTogglePause = async () => {
|
||
const isPaused = !!deployment?.paused;
|
||
const action = isPaused ? i18n.t('kubernetes.deployment.resume') : i18n.t('kubernetes.job.suspend');
|
||
const ok = await confirm({
|
||
title: `${action}滚动更新`,
|
||
description: `确认${action} Deployment "${deploymentName}" 的滚动更新?`,
|
||
confirmText: action,
|
||
});
|
||
if (!ok) return;
|
||
setActionLoading(isPaused ? 'resume' : 'pause');
|
||
try {
|
||
if (isPaused) {
|
||
await kubernetesApi.resumeDeployment(clusterCode, namespace, deploymentName);
|
||
} else {
|
||
await kubernetesApi.pauseDeployment(clusterCode, namespace, deploymentName);
|
||
}
|
||
toast({ title: `${action}成功`, description: `Deployment 滚动更新已${action}` });
|
||
setTimeout(loadData, 1000);
|
||
} catch (e) {
|
||
toast({ title: `${action}失败`, description: e?.message || String(e), variant: 'destructive' });
|
||
} finally { setActionLoading(''); }
|
||
};
|
||
|
||
const handleCICD = async () => {
|
||
// 打开 Webhook 弹窗前拉取 manifest,以获取准确容器名 // Logan.Li
|
||
if (!isCreate && !manifest) await loadManifest();
|
||
setModal({ kind: 'cicd' });
|
||
};
|
||
|
||
const handleUpdateImage = async () => {
|
||
if (!newImage.trim()) return;
|
||
setActionLoading('image');
|
||
try {
|
||
await kubernetesApi.updateDeploymentImage(
|
||
clusterCode, namespace, deploymentName, newImage, cicdContainerName || undefined,
|
||
);
|
||
toast({ title: i18n.t('kubernetes.deploymentdetai.s6df3c303'), description: `正在滚动更新为 ${newImage}` });
|
||
setModal(null);
|
||
loadData();
|
||
} catch (e) {
|
||
toast({ title: i18n.t('kubernetes.deployment.imageUpdateFailed'), description: e?.message || String(e), variant: 'destructive' });
|
||
} finally { setActionLoading(''); }
|
||
};
|
||
|
||
const handleDelete = async () => {
|
||
setActionLoading('delete');
|
||
try {
|
||
// 复用通用删除:通过 PUT 空文档或调用隐式端点。若无专用端点,则提示。
|
||
const tk = useUserStore.getState().token || '';
|
||
const baseUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||
const url = `${baseUrl}/api/v1/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${deploymentName}`;
|
||
const resp = await fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${tk}` } });
|
||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||
toast({ title: i18n.t('kubernetes.msg.deleteSuccess'), description: `${deploymentName} 已删除` });
|
||
setModal(null);
|
||
setTimeout(() => navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments`), 800);
|
||
} catch (e) {
|
||
toast({ title: i18n.t('kubernetes.msg.deleteFailed2'), description: e?.message || String(e), variant: 'destructive' });
|
||
} finally { setActionLoading(''); }
|
||
};
|
||
|
||
// 日志流(Pod 实时日志)
|
||
const handleShowPodLogs = ( pod: any) => setModal({ kind: 'podLogs', pod: pod.name });
|
||
const handleShowPodYaml = ( pod: any) => setModal({ kind: 'podYaml', pod: pod.name });
|
||
const handleShowPodTerminal = ( pod: any) => {
|
||
setTerminalPod(pod.name);
|
||
};
|
||
const handleShowPodFileBrowser = ( pod: any) => {
|
||
setFileBrowserPod(pod.name);
|
||
};
|
||
|
||
const handleDeletePod = async (pod: any) => {
|
||
const ok = await confirm({
|
||
title: i18n.t('kubernetes.pod.deletePod'),
|
||
description: `确认删除 Pod "${pod.name}"?操作不可恢复。`,
|
||
confirmText: i18n.t('kubernetes.storage.delete2'),
|
||
});
|
||
if (!ok) return;
|
||
try {
|
||
await kubernetesApi.deletePod(clusterCode, namespace, pod.name);
|
||
toast({ title: i18n.t('kubernetes.msg.podDeleted') });
|
||
loadData();
|
||
} catch (e) {
|
||
toast({ title: i18n.t('kubernetes.msg.deleteFailed2'), description: (e as any)?.message || String(e), variant: 'destructive' });
|
||
}
|
||
};
|
||
|
||
// ============ 渲染 // Logan.Li ============
|
||
if (loading) {
|
||
return (
|
||
<PageShell variant="workspace" title={deploymentName} subtitle={`${clusterCode} / ${namespace}`} loading loadingLabel={i18n.t('kubernetes.deploymentworkl.seb300b29')} actions={<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>}>
|
||
{null}
|
||
</PageShell>
|
||
);
|
||
}
|
||
|
||
if (!deployment && !isCreate) {
|
||
return (
|
||
<PageShell variant="default" title={deploymentName} subtitle={`${clusterCode} / ${namespace}`} actions={<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>}>
|
||
<Card className="dg-card"><CardContent className="p-12 text-center"><XCircle className="h-8 w-8 mx-auto dg-text-red" /><p className="dg-text-secondary mt-3">未找到 Deployment: {deploymentName}</p></CardContent></Card>
|
||
</PageShell>
|
||
);
|
||
}
|
||
|
||
if (isCreate) {
|
||
const handleCreated = (updated?: any) => {
|
||
const name = updated?.metadata?.name;
|
||
if (name) navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/view/Deployment/${name}`);
|
||
};
|
||
return (
|
||
<PageShell variant="workspace" title={i18n.t('kubernetes.deploymentworkl.se2c85a21')} subtitle={`${clusterCode} / ${namespace}`} actions={<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>}>
|
||
<EditModeContainer clusterCode={clusterCode} namespace={namespace} deploymentName={CREATE_DEPLOYMENT_SLUG} manifest={manifest} isCreate onSaved={handleCreated} onCancel={onBack} />
|
||
</PageShell>
|
||
);
|
||
}
|
||
|
||
const ready = deployment.ready_replicas || deployment.readyReplicas || 0;
|
||
const total = deployment.replicas || 0;
|
||
const isHealthy = ready === total && total > 0;
|
||
|
||
const modeTabs = (
|
||
<div className="inline-flex rounded-md border border-[var(--dg-border)] overflow-hidden">
|
||
<button className={`px-3 py-1.5 text-xs font-medium transition-colors ${mode === 'view' ? 'bg-[var(--dg-bg-active)] text-[var(--dg-text-primary)]' : 'bg-transparent dg-text-secondary hover:text-[var(--dg-text-primary)]'}`} onClick={() => onModeChange('view')}> {i18n.t('common.view')} </button>
|
||
<button className={`px-3 py-1.5 text-xs font-medium transition-colors ${mode === 'edit' ? 'bg-blue-600 text-white' : 'bg-transparent dg-text-secondary hover:text-[var(--dg-text-primary)]'}`} onClick={() => onModeChange('edit')}> {i18n.t('common.edit')} </button>
|
||
<button className={`px-3 py-1.5 text-xs font-medium transition-colors ${mode === 'yaml' ? 'bg-emerald-600 text-white' : 'bg-transparent dg-text-secondary hover:text-[var(--dg-text-primary)]'}`} onClick={() => onModeChange('yaml')}>YAML</button>
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<PageShell
|
||
variant="workspace"
|
||
title={deploymentName}
|
||
subtitle={`${clusterCode} / ${namespace} · Deployment · 镜像: ${deployment?.image || '-'}`}
|
||
actions={(
|
||
<>
|
||
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={onBack}><ArrowLeft className="h-4 w-4 mr-1" /> {i18n.t('common.back')} </Button>
|
||
{modeTabs}
|
||
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadData}><RefreshCw className="h-4 w-4" /></Button>
|
||
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => { setNewImage(deployment?.image || ''); setModal({ kind: 'image' }); }}><ImageIcon className="h-3.5 w-3.5 mr-1" /> {i18n.t('kubernetes.deploymentworkl.sf2e0208b')} </Button>
|
||
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={handleRestart}><RotateCw className="h-3.5 w-3.5 mr-1" /> {i18n.t('agent.agentServiceRestart')} </Button>
|
||
<Button variant="outline" size="sm" className="dg-btn-secondary" disabled={!!actionLoading} onClick={handleTogglePause}>
|
||
{deployment?.paused ? <><Play className="h-3.5 w-3.5 mr-1 dg-text-green" /> {i18n.t('kubernetes.deployment.resume')} </> : <><Pause className="h-3.5 w-3.5 mr-1 dg-text-orange" /> {i18n.t('kubernetes.job.suspend')} </>}
|
||
</Button>
|
||
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={handleCICD}><Rocket className="h-3.5 w-3.5 mr-1 dg-text-purple" />CI/CD</Button>
|
||
<Button variant="outline" size="sm" className="dg-btn-danger" onClick={() => setModal({ kind: 'delete' })}><Trash2 className="h-3.5 w-3.5 mr-1" /> {i18n.t('kubernetes.storage.delete2')} </Button>
|
||
</>
|
||
)}
|
||
>
|
||
<div className="flex items-center gap-2 flex-wrap mb-4">
|
||
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary">{namespace}</Badge>
|
||
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary">Deployment</Badge>
|
||
<Badge className={isHealthy ? 'bg-emerald-500/20 dg-text-green border-emerald-500/30' : 'bg-amber-500/20 dg-text-yellow border-amber-500/30'}>
|
||
{isHealthy ? <CheckCircle className="h-3 w-3 mr-1" /> : <AlertTriangle className="h-3 w-3 mr-1" />}{ready}/{total}
|
||
</Badge>
|
||
{deployment?.paused && <Badge className="bg-orange-500/20 dg-text-orange border-orange-500/30"><Pause className="h-3 w-3 mr-1" /> {i18n.t('kubernetes.deploymentworkl.s8be2521d')} </Badge>}
|
||
</div>
|
||
|
||
{/* ============ 主体:view / edit / yaml ============ */}
|
||
{mode === 'view' ? (
|
||
<DeploymentView
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
deployment={deployment}
|
||
pods={pods}
|
||
replicaSets={replicaSets}
|
||
revisions={revisions}
|
||
onRefresh={loadData}
|
||
onShowPodLogs={handleShowPodLogs}
|
||
onShowPodTerminal={handleShowPodTerminal}
|
||
onShowPodYaml={handleShowPodYaml}
|
||
onShowPodFileBrowser={handleShowPodFileBrowser}
|
||
onDeletePod={handleDeletePod}
|
||
/>
|
||
) : mode === 'yaml' ? (
|
||
/* YAML 编辑模式 — 内联 CodeMirror 编辑器 */
|
||
<YamlViewer
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
resource="deployments"
|
||
name={deploymentName}
|
||
inline
|
||
resourceHint={i18n.t('kubernetes.deploymentworkl.s239604b0')}
|
||
loadYamlFn={loadBundleYaml}
|
||
saveYamlFn={saveBundleYaml}
|
||
onSaved={loadData}
|
||
onClose={() => onModeChange('view')}
|
||
/>
|
||
) : (
|
||
/* 可视化编辑模式 — Tab 系统 */
|
||
<EditModeContainer
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
deploymentName={deploymentName}
|
||
manifest={manifest}
|
||
onSaved={() => { loadManifest(); loadData(); }}
|
||
onCancel={() => onModeChange('view')}
|
||
/>
|
||
)}
|
||
|
||
{/* ============ 弹窗:YAML ============ */}
|
||
{modal?.kind === 'yaml' && (
|
||
<YamlViewer
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
resource="deployments"
|
||
name={deploymentName}
|
||
resourceHint={i18n.t('kubernetes.deploymentworkl.s239604b0')}
|
||
loadYamlFn={loadBundleYaml}
|
||
saveYamlFn={saveBundleYaml}
|
||
onClose={() => setModal(null)}
|
||
onSaved={loadData}
|
||
/>
|
||
)}
|
||
|
||
<ActionModals
|
||
modal={modal}
|
||
deployment={deployment}
|
||
namespace={namespace}
|
||
deploymentName={deploymentName}
|
||
newImage={newImage}
|
||
setNewImage={setNewImage}
|
||
scaleValue={scaleValue}
|
||
setScaleValue={setScaleValue}
|
||
actionLoading={actionLoading}
|
||
onClose={() => setModal(null)}
|
||
onUpdateImage={handleUpdateImage}
|
||
onScaleTo={handleScaleTo}
|
||
onDelete={handleDelete}
|
||
/>
|
||
|
||
{/* ============ CI/CD Webhook 弹窗 ============ */}
|
||
{modal?.kind === 'cicd' && (
|
||
<ModalShell title="CI/CD Webhook" onClose={() => setModal(null)} size="lg">
|
||
<CICDWebhookContent
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
deploymentName={deploymentName}
|
||
image={deployment?.image || newImage || ''}
|
||
containerName={cicdContainerName}
|
||
/>
|
||
</ModalShell>
|
||
)}
|
||
|
||
{/* ============ Pod YAML(只读,精简视图同集群导出) ============ */}
|
||
{modal?.kind === 'podYaml' && (
|
||
<YamlViewer
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
resource="pods"
|
||
name={modal.pod}
|
||
readOnly
|
||
onClose={() => setModal(null)}
|
||
/>
|
||
)}
|
||
|
||
{/* ============ Pod 日志 ============ */}
|
||
{modal?.kind === 'podLogs' && (
|
||
<PodLogsModal
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
podName={modal.pod}
|
||
containers={(pods.find(p => p.name === modal.pod)?.containers || []).map(( c: any) => c.name).filter(Boolean)}
|
||
onClose={() => setModal(null)}
|
||
/>
|
||
)}
|
||
|
||
{/* ============ Pod 终端 ============ */}
|
||
<PodExecTerminal
|
||
open={!!terminalPod}
|
||
onOpenChange={(open) => { if (!open) setTerminalPod(null); }}
|
||
clusterId={clusterId}
|
||
namespace={namespace}
|
||
podName={terminalPod || ''}
|
||
containers={(pods.find(( p: any) => p.name === terminalPod)?.containers || []).map(( c: any) => c.name).filter(Boolean)}
|
||
/>
|
||
{/* ============ Pod 文件浏览器 ============ */}
|
||
<PodFileBrowser
|
||
open={!!fileBrowserPod}
|
||
onOpenChange={(open) => { if (!open) setFileBrowserPod(null); }}
|
||
clusterCode={clusterCode}
|
||
namespace={namespace}
|
||
podName={fileBrowserPod || ''}
|
||
containers={(pods.find(( p: any) => p.name === fileBrowserPod)?.containers || []).map(( c: any) => c.name).filter(Boolean)}
|
||
/>
|
||
</PageShell>
|
||
);
|
||
} |