Files
aiops/frontend/src/components/kubernetes/KubernetesClusterDetail.tsx
T

131 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}