批量转换 163 个文件,3204 处中文替换为 t() 调用: - Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换 - Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换 - Deploy: 流水线/发布/凭证/RBAC 全部转换 - Server/Cloud/Agent/Task/Layout/UI: 全部转换 - 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys) - tsc 编译零错误
242 lines
9.9 KiB
TypeScript
242 lines
9.9 KiB
TypeScript
// 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<JsonData>(null);
|
|
const [pods, setPods] = useState<JsonData[]>([]);
|
|
const [hpas, setHpas] = useState<JsonData[]>([]);
|
|
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<PodWithContainers | null>(null);
|
|
const [execOpen, setExecOpen] = useState(false);
|
|
const [logsViewerPod, setLogsViewerPod] = useState<PodWithContainers | null>(null);
|
|
const [logsViewerOpen, setLogsViewerOpen] = useState(false);
|
|
const [fileBrowserPod, setFileBrowserPod] = useState<PodWithContainers | null>(null);
|
|
const [fileBrowserOpen, setFileBrowserOpen] = useState(false);
|
|
const [yamlPodName, setYamlPodName] = useState<string | null>(null);
|
|
const [logsTabPod, setLogsTabPod] = useState<string>('');
|
|
|
|
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 (
|
|
<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">{t('common.loading')}</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!deployment) {
|
|
return (
|
|
<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>
|
|
<Button variant="outline" className="mt-4" onClick={onBack}>{t('common.back')}</Button>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const ready = deployment.ready_replicas || deployment.readyReplicas || 0;
|
|
const total = deployment.replicas || 0;
|
|
const available = deployment.available_replicas || deployment.availableReplicas || 0;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<DeploymentOverview
|
|
deploymentName={deploymentName}
|
|
namespace={namespace}
|
|
deployment={deployment}
|
|
hpas={hpas}
|
|
clusterCode={clusterCode}
|
|
ready={ready}
|
|
total={total}
|
|
available={available}
|
|
isHealthy={ready === total}
|
|
actionLoading={actionLoading}
|
|
newImage={newImage}
|
|
showImageUpdate={showImageUpdate}
|
|
onBack={onBack}
|
|
onRefresh={loadData}
|
|
onScale={handleScale}
|
|
onRestart={handleRestart}
|
|
onNewImageChange={setNewImage}
|
|
onToggleImageUpdate={() => setShowImageUpdate((v) => !v)}
|
|
onUpdateImage={handleUpdateImage}
|
|
/>
|
|
|
|
<Tabs defaultValue="pods">
|
|
<TabsList className="bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)]">
|
|
<TabsTrigger value="pods" className="data-[state=active]:bg-[var(--dg-bg-active)]">
|
|
Pod 列表 ({pods.length})
|
|
</TabsTrigger>
|
|
<TabsTrigger value="logs" className="data-[state=active]:bg-[var(--dg-bg-active)]">
|
|
<FileText className="h-4 w-4 mr-1" />日志
|
|
</TabsTrigger>
|
|
<TabsTrigger value="hpa" className="data-[state=active]:bg-[var(--dg-bg-active)]">
|
|
<Gauge className="h-4 w-4 mr-1" />HPA ({hpas.length})
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<DeploymentPodsTab
|
|
pods={pods}
|
|
onOpenLogsViewer={(pod) => { setLogsViewerPod(pod); setLogsViewerOpen(true); }}
|
|
onOpenExec={(pod) => { setExecPod(pod); setExecOpen(true); }}
|
|
onOpenFileBrowser={(pod) => { setFileBrowserPod(pod); setFileBrowserOpen(true); }}
|
|
onOpenYaml={setYamlPodName}
|
|
/>
|
|
<DeploymentLogsTab
|
|
pods={pods}
|
|
logTail={logTail}
|
|
onLogTailChange={setLogTail}
|
|
logsTabPod={logsTabPod}
|
|
onLogsTabPodChange={setLogsTabPod}
|
|
onOpenLogsViewer={(name, containers) => { setLogsViewerPod({ name, containers }); setLogsViewerOpen(true); }}
|
|
/>
|
|
<DeploymentHpaTab
|
|
hpas={hpas}
|
|
clusterCode={clusterCode}
|
|
namespace={namespace}
|
|
deploymentName={deploymentName}
|
|
onCreated={loadData}
|
|
onDeleteHpa={handleHpaToggle}
|
|
/>
|
|
</Tabs>
|
|
|
|
{yamlPodName && (
|
|
<YamlViewer
|
|
clusterCode={clusterCode}
|
|
namespace={namespace}
|
|
resource="pods"
|
|
name={yamlPodName}
|
|
readOnly
|
|
onClose={() => setYamlPodName(null)}
|
|
/>
|
|
)}
|
|
|
|
<DeploymentPodModals
|
|
clusterCode={clusterCode}
|
|
namespace={namespace}
|
|
execPod={execPod}
|
|
execOpen={execOpen}
|
|
onExecOpenChange={(o) => { 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); }}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|