Co-authored-by: xiuting.xu <xiutingxt.xu@gmail.com> Reviewed-on: #28 Reviewed-by: yuyr <yuyr@zgclab.edu.cn> Reviewed-by: huhy <husteryezi@163.com> Reviewed-by: sundapeng <sundp@mail.zgclab.edu.cn>
133 lines
4.0 KiB
JavaScript
133 lines
4.0 KiB
JavaScript
import { useState, useEffect } from "react";
|
|
import {
|
|
Drawer,
|
|
Text,
|
|
Loader,
|
|
Center,
|
|
ScrollArea,
|
|
Group,
|
|
Divider,
|
|
ThemeIcon,
|
|
Stack,
|
|
ActionIcon,
|
|
} from "@mantine/core";
|
|
import { IconRefresh } from "@tabler/icons-react";
|
|
import { healthStatus } from "../config/status";
|
|
import { apiRequest } from "../config/request";
|
|
import { MASTER_API } from "../config/api";
|
|
|
|
import NodeConfigCard from "./NodeConfigCard";
|
|
import NodeLabelCard from "./NodeLabelCard";
|
|
import NodeMetaCard from "./NodeMetaCard";
|
|
import NodeHealthCard from "./NodeHealthCard";
|
|
|
|
export default function NodeDetailDrawer({ opened, nodeId, onClose }) {
|
|
const [node, setNode] = useState(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const fetchNodeDetail = async (id) => {
|
|
if (!id) return;
|
|
setLoading(true);
|
|
try {
|
|
const res = await apiRequest(MASTER_API.DETAIL(id));
|
|
setNode(res);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (opened && nodeId) fetchNodeDetail(nodeId);
|
|
}, [opened, nodeId]);
|
|
|
|
return (
|
|
<Drawer
|
|
opened={opened}
|
|
onClose={onClose}
|
|
position="right"
|
|
size="lg"
|
|
title="节点详情"
|
|
padding="lg"
|
|
overlayProps={{ backgroundOpacity: 0.4, blur: 4 }}
|
|
>
|
|
{loading && !node ? (
|
|
<Center h={200}>
|
|
<Loader size="sm" />
|
|
</Center>
|
|
) : node ? (
|
|
<div style={{ height: "90vh", display: "flex", flexDirection: "column" }}>
|
|
{/* 固定头部基础信息 */}
|
|
<div
|
|
style={{
|
|
position: "sticky",
|
|
top: 0,
|
|
background: "white",
|
|
zIndex: 10,
|
|
paddingBottom: 8,
|
|
}}
|
|
>
|
|
<Group spacing="sm" align="center" position="apart">
|
|
<Group spacing="sm" align="center">
|
|
<ThemeIcon
|
|
size="lg"
|
|
radius="xl"
|
|
color={healthStatus(node.status).color}
|
|
variant="light"
|
|
>
|
|
{healthStatus(node.status).icon}
|
|
</ThemeIcon>
|
|
|
|
<Text fw={700} size="xl">{node.name}</Text>
|
|
<Text c="dimmed">{node.type}</Text>
|
|
<Text c={healthStatus(node.status).color}>{node.status}</Text>
|
|
<Text c="dimmed" size="sm">
|
|
最近上报时间: {new Date(node.last_report).toLocaleString()}
|
|
</Text>
|
|
</Group>
|
|
|
|
{/* 刷新按钮固定在右侧 */}
|
|
<ActionIcon
|
|
color="blue"
|
|
variant="light"
|
|
onClick={() => fetchNodeDetail(node.id)}
|
|
disabled={loading}
|
|
>
|
|
<IconRefresh size={18} />
|
|
</ActionIcon>
|
|
</Group>
|
|
|
|
<Divider my="sm" />
|
|
</div>
|
|
|
|
{/* 滚动内容 */}
|
|
<ScrollArea style={{ flex: 1 }}>
|
|
<Stack spacing="md">
|
|
|
|
{/* 元数据 */}
|
|
<NodeMetaCard node={node} />
|
|
|
|
{/* 健康信息 */}
|
|
<NodeHealthCard node={node} />
|
|
|
|
{/* 配置信息 */}
|
|
<NodeConfigCard nodeId={node.id} config={node.config || {}} onSaved={() => fetchNodeDetail(node.id)} />
|
|
|
|
{/* 标签信息 */}
|
|
<NodeLabelCard nodeId={node.id} labels={Array.isArray(node.label) ? node.label : []} onSaved={() => fetchNodeDetail(node.id)} />
|
|
|
|
{/* 其他基础信息展示 */}
|
|
<Stack spacing="xs">
|
|
<Text fw={500}>注册时间: <Text span c="dimmed">{new Date(node.register_time).toLocaleString()}</Text></Text>
|
|
<Text fw={500}>最近上报时间: <Text span c="dimmed">{new Date(node.last_report).toLocaleString()}</Text></Text>
|
|
<Text fw={500}>最后更新时间: <Text span c="dimmed">{new Date(node.last_updated).toLocaleString()}</Text></Text>
|
|
</Stack>
|
|
</Stack>
|
|
</ScrollArea>
|
|
</div>
|
|
) : (
|
|
<Text c="dimmed">暂无数据</Text>
|
|
)}
|
|
</Drawer>
|
|
);
|
|
}
|