146 lines
5.4 KiB
JavaScript
146 lines
5.4 KiB
JavaScript
import { useState, useEffect } from "react";
|
||
import { Card, Table, Button, Loader, Center, Group, Anchor, Text } from "@mantine/core";
|
||
import { Link } from "react-router-dom";
|
||
import NodeStatus from "./NodeStatus";
|
||
import PaginationControl from "./PaginationControl";
|
||
import { apiRequest } from "../config/request";
|
||
import { MASTER_API, EXTERNAL_HOST } from "../config/api";
|
||
|
||
export function NodeTable({
|
||
withSearch = false,
|
||
withPagination = false,
|
||
withActions = false,
|
||
clusterData = null, // 直接传入数据(Dashboard 用)
|
||
fetchDetail, // 详情函数(NodePage 用)
|
||
title,
|
||
viewMoreLink,
|
||
}) {
|
||
const [nodes, setNodes] = useState([]);
|
||
const [page, setPage] = useState(1);
|
||
const [pageSize, setPageSize] = useState(5);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
// 拉取节点数据(仅 NodePage 使用)
|
||
const fetchNodes = async (params = {}) => {
|
||
if (!withPagination && !withSearch) return; // Dashboard 只用 clusterData
|
||
setLoading(true);
|
||
try {
|
||
const query = new URLSearchParams({
|
||
page: params.page || page,
|
||
limit: params.pageSize || pageSize,
|
||
}).toString();
|
||
|
||
const result = await apiRequest(`${MASTER_API.LIST}?${query}`);
|
||
setNodes(result);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
// 初始化加载
|
||
useEffect(() => {
|
||
if (withPagination || withSearch) {
|
||
fetchNodes();
|
||
} else if (clusterData) {
|
||
setNodes(clusterData || []);
|
||
}
|
||
}, [clusterData]);
|
||
|
||
// 表格行
|
||
const rows = nodes.map((node) => (
|
||
<Table.Tr key={node.id}>
|
||
<Table.Td>{node.id}</Table.Td>
|
||
<Table.Td>{node.name}</Table.Td>
|
||
<Table.Td><NodeStatus status={node.status} /></Table.Td>
|
||
<Table.Td>{node.type}</Table.Td>
|
||
<Table.Td>{node.version}</Table.Td>
|
||
{withActions && (
|
||
<Table.Td>
|
||
<Group spacing="xs">
|
||
{/* 查看详情 */}
|
||
<Button
|
||
size="xs"
|
||
variant="light"
|
||
onClick={() => fetchDetail && fetchDetail(node.id)}
|
||
>
|
||
查看详情
|
||
</Button>
|
||
|
||
{/* Grafana Dashboard 链接 */}
|
||
<Button
|
||
size="xs"
|
||
variant="outline"
|
||
component="a"
|
||
href={`${EXTERNAL_HOST.GRAFANA}/d/node_gpu_metrics/node-and-gpu-metrics?var-hostname=${encodeURIComponent(node.name)}`}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
Grafana
|
||
</Button>
|
||
</Group>
|
||
</Table.Td>
|
||
)}
|
||
</Table.Tr>
|
||
));
|
||
|
||
return (
|
||
<Card shadow="sm" radius="md" p="lg">
|
||
{/* 标题 + 查看更多 */}
|
||
{(title || viewMoreLink) && (
|
||
<Group position="apart" mb="sm">
|
||
{title && <Text fw={700} size="lg">{title}</Text>}
|
||
{viewMoreLink && (
|
||
<Anchor component={Link} to={viewMoreLink} size="sm" underline>
|
||
查看更多
|
||
</Anchor>
|
||
)}
|
||
</Group>
|
||
)}
|
||
{/* 搜索区域 */}
|
||
{withSearch && (
|
||
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
|
||
<Button onClick={() => fetchNodes()} variant="outline">刷新列表</Button>
|
||
</div>
|
||
)}
|
||
|
||
{loading ? (
|
||
<Center h={200}><Loader size="lg" /></Center>
|
||
) : (
|
||
<>
|
||
<Table striped highlightOnHover withTableBorder>
|
||
<Table.Thead>
|
||
<Table.Tr>
|
||
<Table.Th>ID</Table.Th>
|
||
<Table.Th>名称</Table.Th>
|
||
<Table.Th>状态</Table.Th>
|
||
<Table.Th>类型</Table.Th>
|
||
<Table.Th>版本</Table.Th>
|
||
{withActions && <Table.Th>操作</Table.Th>}
|
||
</Table.Tr>
|
||
</Table.Thead>
|
||
<Table.Tbody>{rows}</Table.Tbody>
|
||
</Table>
|
||
|
||
{withPagination && (
|
||
<PaginationControl
|
||
page={page}
|
||
pageSize={pageSize}
|
||
hasPrevPage={page > 1}
|
||
hasNextPage={nodes.length === pageSize}
|
||
onPageChange={(p) => {
|
||
setPage(p);
|
||
fetchNodes({ page: p });
|
||
}}
|
||
onPageSizeChange={(size) => {
|
||
setPageSize(size);
|
||
setPage(1);
|
||
fetchNodes({ page: 1, pageSize: size });
|
||
}}
|
||
/>
|
||
)}
|
||
</>
|
||
)}
|
||
</Card>
|
||
);
|
||
}
|