// DeploymentDetail.tsx — Deployment 详情页编排组件 // Logan.Li import { useState, useEffect, useCallback } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { FileText, Gauge, Loader2, XCircle } from 'lucide-react'; import { kubernetesApi } from '@/api'; import { useToast } from '@/hooks/use-toast'; import { useConfirm } from '@/components/ui/confirm-dialog'; import type { DeploymentDetailProps, PodWithContainers } from './deployment-detail/types'; import { DeploymentOverview } from './deployment-detail/DeploymentOverview'; import { DeploymentPodsTab } from './deployment-detail/DeploymentPodsTab'; import { DeploymentLogsTab } from './deployment-detail/DeploymentLogsTab'; import { DeploymentHpaTab } from './deployment-detail/DeploymentHpaTab'; import { DeploymentPodModals } from './deployment-detail/DeploymentPodModals'; import { YamlViewer } from './YamlViewer'; import type { JsonData } from '@/types/common'; import { useTranslation } from 'react-i18next'; export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBack }: DeploymentDetailProps) { const { t } = useTranslation(); const { toast } = useToast(); const confirm = useConfirm(); const [deployment, setDeployment] = useState(null); const [pods, setPods] = useState([]); const [hpas, setHpas] = useState([]); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(''); const [, setScaleValue] = useState(0); const [newImage, setNewImage] = useState(''); const [showImageUpdate, setShowImageUpdate] = useState(false); const [logTail, setLogTail] = useState(200); const [execPod, setExecPod] = useState(null); const [execOpen, setExecOpen] = useState(false); const [logsViewerPod, setLogsViewerPod] = useState(null); const [logsViewerOpen, setLogsViewerOpen] = useState(false); const [fileBrowserPod, setFileBrowserPod] = useState(null); const [fileBrowserOpen, setFileBrowserOpen] = useState(false); const [yamlPodName, setYamlPodName] = useState(null); const [logsTabPod, setLogsTabPod] = useState(''); const loadData = useCallback(async () => { setLoading(true); try { const [depRes, podRes, hpaRes] = await Promise.all([ kubernetesApi.getNamespaceDeployments(clusterCode, namespace).catch(() => []), kubernetesApi.getNamespacePods(clusterCode, namespace).catch(() => []), kubernetesApi.getHPAs(clusterCode, namespace).catch(() => ({ items: [] })), ]); const toArray = (v: JsonData) => (Array.isArray(v) ? v : (v?.list || v?.items || [])); const allDeps = toArray(depRes); const allPods = toArray(podRes); const allHpas = toArray(hpaRes); const dep = allDeps.find((d: JsonData) => d.name === deploymentName && d.namespace === namespace); setDeployment(dep || null); if (dep) { setScaleValue(dep.replicas || 0); setNewImage(dep.image || ''); } setPods(allPods.filter((p: JsonData) => p.namespace === namespace && p.name.startsWith(deploymentName))); setHpas(allHpas.filter((h: JsonData) => h.target_name === deploymentName && h.namespace === namespace)); } catch (e) { console.error(t('kubernetes.deploymentdetail.sb46ea9'), e); } finally { setLoading(false); } }, [clusterCode, namespace, deploymentName]); useEffect(() => { loadData(); }, [loadData]); 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: t('kubernetes.deploymentdetail.s0b3ea1'), description: t('kubernetes.deploymentdetail.sacbcbd', { newReplicas: newReplicas }) }); loadData(); } catch (e) { toast({ title: t('common.operationFailed'), description: e?.message || String(e), variant: 'destructive' }); } finally { setActionLoading(''); } }; const handleRestart = async () => { setActionLoading('restart'); try { await kubernetesApi.restartDeployment(clusterCode, namespace, deploymentName); toast({ title: t('kubernetes.deploymentdetail.s68a700'), description: t('kubernetes.deploymentdetail.sd18761') }); } catch (e) { toast({ title: t('kubernetes.deployment.restartFailed'), description: e?.message || String(e), variant: 'destructive' }); } finally { setActionLoading(''); } }; const handleUpdateImage = async () => { if (!newImage.trim()) return; setActionLoading('image'); try { await kubernetesApi.updateDeploymentImage(clusterCode, namespace, deploymentName, newImage); toast({ title: t('kubernetes.deploymentdetail.s6df3c3'), description: t('kubernetes.deploymentdetail.sa982c3', { newImage: newImage }) }); setShowImageUpdate(false); loadData(); } catch (e) { toast({ title: t('alert.updateFailed'), description: e?.message || String(e), variant: 'destructive' }); } finally { setActionLoading(''); } }; const handleHpaToggle = async (hpa: JsonData) => { const ok = await confirm({ title: t('kubernetes.hpa.deleteHpa'), description: t('kubernetes.deploymentdetail.s209f7e', { hpa.name: hpa.name }), confirmText: t('common.delete'), variant: 'destructive', }); if (!ok) return; try { await kubernetesApi.deleteHPA(clusterCode, namespace, hpa.name); toast({ title: t('kubernetes.deploymentdetail.s0f958f') }); loadData(); } catch (e) { toast({ title: t('common.deleteFailed'), description: e?.message || String(e), variant: 'destructive' }); } }; if (loading) { return (

{t('common.loading')}

); } if (!deployment) { return (

未找到 Deployment: {deploymentName}

); } const ready = deployment.ready_replicas || deployment.readyReplicas || 0; const total = deployment.replicas || 0; const available = deployment.available_replicas || deployment.availableReplicas || 0; return (
setShowImageUpdate((v) => !v)} onUpdateImage={handleUpdateImage} /> Pod 列表 ({pods.length}) 日志 HPA ({hpas.length}) { setLogsViewerPod(pod); setLogsViewerOpen(true); }} onOpenExec={(pod) => { setExecPod(pod); setExecOpen(true); }} onOpenFileBrowser={(pod) => { setFileBrowserPod(pod); setFileBrowserOpen(true); }} onOpenYaml={setYamlPodName} /> { setLogsViewerPod({ name, containers }); setLogsViewerOpen(true); }} /> {yamlPodName && ( setYamlPodName(null)} /> )} { setExecOpen(o); if (!o) setExecPod(null); }} logsViewerPod={logsViewerPod} logsViewerOpen={logsViewerOpen} onLogsViewerOpenChange={(o) => { setLogsViewerOpen(o); if (!o) setLogsViewerPod(null); }} fileBrowserPod={fileBrowserPod} fileBrowserOpen={fileBrowserOpen} onFileBrowserOpenChange={(o) => { setFileBrowserOpen(o); if (!o) setFileBrowserPod(null); }} />
); }