Skip to main content
Glama
InspectionSummary.tsx11.8 kB
import React, { useState, useEffect, useCallback } from 'react'; import { Alert, Card, DatePicker, Form, Spin, Table, Typography, Space, Dropdown, MenuProps, Tag, Drawer, Select } from "antd"; import dayjs, { Dayjs } from 'dayjs'; import { fetcher } from '@/components/Amis/fetcher'; import { replacePlaceholders, toUrlSafeBase64 } from '@/utils/utils'; import { DownOutlined } from '@ant-design/icons'; import InspectionEventListComponent from './InspectionEventList'; import { useClusterOptions } from './useClusterOptions'; const { Title, Text } = Typography; interface InspectionSummaryComponentProps { schedule_id: string; data: Record<string, any>; } const InspectionSummaryComponent = React.forwardRef<HTMLDivElement, InspectionSummaryComponentProps>(({ schedule_id, data }, _) => { // 表单状态 const [form] = Form.useForm(); const [startTime, setStartTime] = useState<Dayjs>(() => dayjs().startOf('day')); const [endTime, setEndTime] = useState<Dayjs>(() => dayjs().add(1, 'day').startOf('day')); const [loading, setLoading] = useState(false); const [summaryData, setSummaryData] = useState<any>({}); const [error, setError] = useState<string | null>(null); const [drawerOpen, setDrawerOpen] = useState(false); const [drawerRecordId, setDrawerRecordId] = useState<number | null>(null); const { options: clusterOptions, loading: clusterLoading } = useClusterOptions(); const [cluster, setCluster] = useState<string | undefined>(undefined); let realScheduleId = "" // 处理 schedule_id 占位符 if (schedule_id !== undefined) { // 处理 schedule_id 占位符 realScheduleId = replacePlaceholders(schedule_id, data) || ""; } // 查询API,使用fetcher const fetchSummary = useCallback((params?: { startTime?: Dayjs, endTime?: Dayjs }) => { setLoading(true); setError(null); const sTime = (params?.startTime || startTime).format('YYYY-MM-DDTHH:mm:ss') + 'Z'; const eTime = (params?.endTime || endTime).format('YYYY-MM-DDTHH:mm:ss') + 'Z'; let url = ''; let clusterBase64 = ""; if (cluster) { clusterBase64 = toUrlSafeBase64(cluster) } url = `/admin/inspection/schedule/id/${realScheduleId}/summary/cluster/${clusterBase64}/start_time/${encodeURIComponent(sTime)}/end_time/${encodeURIComponent(eTime)}`; fetcher({ url, method: 'post' }) .then((response: any) => { if (response?.data?.data) { setSummaryData(response.data.data); } else { setSummaryData({}); setError('未获取到数据'); } }) .catch((err: any) => { setError(err.message || '未知错误'); setSummaryData({}); }) .finally(() => { setLoading(false); }); }, [startTime, endTime, cluster, realScheduleId]); // 外部参数变化自动刷新 useEffect(() => { fetchSummary(); }, [startTime, endTime, fetchSummary]); // cluster 变化时刷新 useEffect(() => { fetchSummary(); }, [cluster]); // antd表格列定义 const latestRunColumns = [ { title: '资源类型', dataIndex: 'kind', key: 'kind' }, { title: '异常数', dataIndex: 'error_count', key: 'error_count', render: (text: any, _: any) => ( <span style={{ color: '#ff4d4f', cursor: 'pointer' }} onClick={() => { setDrawerRecordId(latest_run.record_id); setDrawerOpen(true); }} >{text}</span> ) } ]; const clusterColumns = [ { title: '资源类型', dataIndex: 'kind', key: 'kind' }, { title: '异常数', dataIndex: 'error_count', key: 'error_count' } ]; const { total_runs, total_clusters, latest_run = {}, clusters = [], total_schedules } = summaryData || {}; // 时间快捷选项 const quickRanges = [ { label: "1月", value: { start: dayjs().subtract(1, 'month').startOf('day'), end: dayjs().endOf('day') } }, { label: "1周", value: { start: dayjs().subtract(7, 'day').startOf('day'), end: dayjs().endOf('day') } }, { label: "2天", value: { start: dayjs().subtract(2, 'day').startOf('day'), end: dayjs().endOf('day') } }, { label: "1天", value: { start: dayjs().subtract(1, 'day').startOf('day'), end: dayjs().endOf('day') } }, { label: "6小时", value: { start: dayjs().subtract(6, 'hour'), end: dayjs() } }, { label: "1小时", value: { start: dayjs().subtract(1, 'hour'), end: dayjs() } }, ]; const quickMenuProps: MenuProps = { items: quickRanges.map((item, idx) => ({ key: idx.toString(), label: item.label, })), onClick: (info) => { const idx = Number(info.key); const range = quickRanges[idx]; setStartTime(range.value.start); setEndTime(range.value.end); form.setFieldsValue({ startTime: range.value.start, endTime: range.value.end }); fetchSummary({ startTime: range.value.start, endTime: range.value.end }); }, }; return ( <div> <Card style={{ marginBottom: 16 }}> <Form form={form} layout="inline" initialValues={{ startTime, endTime, cluster }} onFinish={values => { setStartTime(values.startTime); setEndTime(values.endTime); setCluster(values.cluster || undefined); fetchSummary(values); }} > <Form.Item label="起始时间" name="startTime" rules={[{ required: true, message: '请选择起始时间' }]}> <DatePicker showTime format="YYYY-MM-DD HH:mm" value={startTime} allowClear={false} /> </Form.Item> <Form.Item label="结束时间" name="endTime" rules={[{ required: true, message: '请选择结束时间' }]}> <DatePicker showTime format="YYYY-MM-DD HH:mm" value={endTime} allowClear={false} /> </Form.Item> <Form.Item label="集群" name="cluster" initialValue={cluster || ''}> <Select style={{ minWidth: 220 }} loading={clusterLoading} allowClear placeholder="全部集群" onChange={val => { setCluster(val || undefined); form.submit(); }} > <Select.Option value="">全部集群</Select.Option> {clusterOptions.map(opt => ( <Select.Option key={opt.value} value={opt.value}>{opt.label}</Select.Option> ))} </Select> </Form.Item> <Form.Item> <Dropdown.Button icon={<DownOutlined />} menu={quickMenuProps} placement="bottomLeft" onClick={() => form.submit()} type="primary" loading={loading} > 查询 </Dropdown.Button> </Form.Item> </Form> </Card> {error && <Alert type="error" message={error} style={{ marginBottom: 8 }} showIcon />} <Spin spinning={loading} tip="加载中..."> <Card style={{ marginBottom: 16 }}> <Space> <Text strong>总执行次数:</Text> <Text>{total_runs ?? '-'}</Text> <Text strong>总集群数:</Text> <Text>{total_clusters ?? '-'}</Text> {total_schedules !== undefined && ( <> <Text strong>运行巡检计划数:</Text> <Text>{total_schedules}</Text> </> )} </Space> </Card> {latest_run && latest_run.record_id && ( <Card title={ <span> <span style={{ marginRight: 8 }}> <b>最后执行信息</b> </span> <span style={{ marginRight: 8 }}> <b>计划ID:</b> <Tag color="blue">{latest_run.schedule_id ?? '-'}</Tag> </span> <span style={{ marginRight: 8, cursor: 'pointer' }} onClick={() => { setDrawerRecordId(latest_run.record_id); setDrawerOpen(true); }} > <b>记录ID:</b> <Tag color="green">{latest_run.record_id}</Tag> </span> <span> <b>执行时间:</b> <Tag color="volcano">{latest_run.run_time ? dayjs(latest_run.run_time).format('YYYY-MM-DD HH:mm:ss') : '-'}</Tag> </span> </span> } style={{ marginBottom: 16 }} > <Table columns={latestRunColumns} dataSource={latest_run.kinds || []} rowKey={(r: any) => r.kind} pagination={false} size="small" /> </Card> )} <Title level={5} style={{ margin: '16px 0 8px 0' }}>汇总数据:</Title> {clusters.length === 0 && <Text type="secondary">暂无集群数据</Text>} {clusters.map((cluster: any, idx: number) => ( <Card key={idx} title={<span>集群:{cluster.cluster} (执行{cluster.run_count}次)</span>} style={{ marginBottom: 16 }}> <Table columns={clusterColumns} dataSource={cluster.kinds || []} rowKey={(r: any) => r.kind} pagination={false} size="small" /> </Card> ))} </Spin> <Drawer title="事件明细" width={900} open={drawerOpen} onClose={() => setDrawerOpen(false)} destroyOnClose > {drawerRecordId && <InspectionEventListComponent record_id={`${drawerRecordId}`} />} </Drawer> </div> ); }); export default InspectionSummaryComponent;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/weibaohui/k8m'

If you have feedback or need assistance with the MCP directory API, please join our Discord server