131 lines
6.5 KiB
TypeScript
131 lines
6.5 KiB
TypeScript
import { useState, useEffect, useMemo } from 'react';
|
||
import { useTranslation } from 'react-i18next';
|
||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||
import { Cloud, XCircle } from 'lucide-react';
|
||
import { kubernetesApi } from '@/api';
|
||
import type { JsonData } from '@/types/common';
|
||
import { computeClusterMetrics, buildTrendData } from './cluster-detail/types';
|
||
import { OverviewTab } from './cluster-detail/OverviewTab';
|
||
import { NodesTab } from './cluster-detail/NodesTab';
|
||
import { PodsTab } from './cluster-detail/PodsTab';
|
||
import { DeploymentsTab } from './cluster-detail/DeploymentsTab';
|
||
import { ServicesTab } from './cluster-detail/ServicesTab';
|
||
import { IngressesTab } from './cluster-detail/IngressesTab';
|
||
import { EventsTab } from './cluster-detail/EventsTab';
|
||
|
||
export function KubernetesClusterDetail({ cluster, onClose }: { cluster?: JsonData; onClose: () => void }) {
|
||
const { t } = useTranslation();
|
||
const [nodes, setNodes] = useState<JsonData[]>([]);
|
||
const [pods, setPods] = useState<JsonData[]>([]);
|
||
const [deployments, setDeployments] = useState<JsonData[]>([]);
|
||
const [services, setServices] = useState<JsonData[]>([]);
|
||
const [ingresses, setIngresses] = useState<JsonData[]>([]);
|
||
|
||
// 从节点数据计算集群级 CPU/内存使用率(实时快照)
|
||
const clusterMetrics = useMemo(() => computeClusterMetrics(nodes), [nodes]);
|
||
|
||
// 构造趋势数据(多点),避免单点导致"光点"效果
|
||
const trendData = useMemo(() => buildTrendData(clusterMetrics, t), [clusterMetrics, t]);
|
||
|
||
const loadClusterDetail = async (clusterCode: string) => {
|
||
try {
|
||
// 后端 ListNodes/ListPods/etc 返回 data 直接为数组(非 {list,total})
|
||
// 统一使用 Kuboard code 路由 (ROADMAP A1)
|
||
const [nodesRes, podsRes, depRes, svcRes, ingRes] = await Promise.all([
|
||
kubernetesApi.getNodes(clusterCode).catch(() => []),
|
||
kubernetesApi.getPods(clusterCode).catch(() => []),
|
||
kubernetesApi.getDeployments(clusterCode).catch(() => []),
|
||
kubernetesApi.getServices(clusterCode).catch(() => []),
|
||
kubernetesApi.getIngresses(clusterCode).catch(() => []),
|
||
]);
|
||
const toArray = (v: JsonData) => (Array.isArray(v) ? v : (v?.list || []));
|
||
setNodes(toArray(nodesRes));
|
||
setPods(toArray(podsRes));
|
||
setDeployments(toArray(depRes));
|
||
setServices(toArray(svcRes));
|
||
setIngresses(toArray(ingRes));
|
||
} catch {
|
||
// load failed
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (cluster?.code) {
|
||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||
loadClusterDetail(cluster.code);
|
||
}
|
||
}, [cluster?.code]);
|
||
|
||
return (
|
||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<Cloud className="h-6 w-6 text-[var(--dg-blue)]" />
|
||
<div>
|
||
<CardTitle className="text-[var(--dg-text-primary)]">{cluster.name}</CardTitle>
|
||
<CardDescription className="text-[var(--dg-text-muted)]">
|
||
{cluster.version} · {cluster.node_count || 0} {t('k8s.cluster.nodeCount')} · {t('k8s.cluster.created')}{' '}
|
||
{cluster.created_at ? new Date(cluster.created_at).toLocaleDateString() : '-'}
|
||
</CardDescription>
|
||
</div>
|
||
</div>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
|
||
onClick={onClose}
|
||
>
|
||
<XCircle className="h-4 w-4 mr-2" />
|
||
{t('common.close')}
|
||
</Button>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Tabs defaultValue="overview" className="w-full">
|
||
<TabsList className="bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] flex-wrap h-auto">
|
||
<TabsTrigger value="overview" className="data-[state=active]:bg-[var(--dg-bg-active)] data-[state=active]:text-[var(--dg-text-primary)]">
|
||
{t('k8s.tab.overview')}
|
||
</TabsTrigger>
|
||
<TabsTrigger value="nodes" className="data-[state=active]:bg-[var(--dg-bg-active)] data-[state=active]:text-[var(--dg-text-primary)]">
|
||
{t('k8s.tab.nodes')} ({nodes.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="pods" className="data-[state=active]:bg-[var(--dg-bg-active)] data-[state=active]:text-[var(--dg-text-primary)]">
|
||
{t('k8s.tab.pods')} ({pods.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="deployments" className="data-[state=active]:bg-[var(--dg-bg-active)] data-[state=active]:text-[var(--dg-text-primary)]">
|
||
{t('k8s.tab.deployments')} ({deployments.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="services" className="data-[state=active]:bg-[var(--dg-bg-active)] data-[state=active]:text-[var(--dg-text-primary)]">
|
||
{t('k8s.tab.services')} ({services.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="ingresses" className="data-[state=active]:bg-[var(--dg-bg-active)] data-[state=active]:text-[var(--dg-text-primary)]">
|
||
{t('k8s.tab.ingresses')} ({ingresses.length})
|
||
</TabsTrigger>
|
||
<TabsTrigger value="events" className="data-[state=active]:bg-[var(--dg-bg-active)] data-[state=active]:text-[var(--dg-text-primary)]">
|
||
{t('k8s.tab.events')}
|
||
</TabsTrigger>
|
||
</TabsList>
|
||
|
||
<TabsContent value="overview" className="mt-4">
|
||
<OverviewTab
|
||
cluster={cluster}
|
||
trendData={trendData}
|
||
metrics={clusterMetrics}
|
||
onRefresh={() => cluster?.id && loadClusterDetail(cluster.id)}
|
||
/>
|
||
</TabsContent>
|
||
<TabsContent value="nodes" className="mt-4"><NodesTab nodes={nodes} /></TabsContent>
|
||
<TabsContent value="pods" className="mt-4"><PodsTab pods={pods} /></TabsContent>
|
||
<TabsContent value="deployments" className="mt-4"><DeploymentsTab deployments={deployments} /></TabsContent>
|
||
<TabsContent value="services" className="mt-4"><ServicesTab services={services} /></TabsContent>
|
||
<TabsContent value="ingresses" className="mt-4"><IngressesTab ingresses={ingresses} /></TabsContent>
|
||
<TabsContent value="events" className="mt-4"><EventsTab /></TabsContent>
|
||
</Tabs>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|