Files
argus/src/web/src/components/NodeDetailDrawer.jsx
xuxt d1b89c0cf6 dev_1.0.0_xuxt_2 更新反向代理,打包镜像,以及README文档 (#28)
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>
2025-10-20 09:45:32 +08:00

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>
);
}