From f3eca3b21410e71299f9d44e426afc77e2a4d941 Mon Sep 17 00:00:00 2001 From: GraceWalk Date: Fri, 21 Oct 2022 11:43:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20ConsumerGroup=20=E5=88=97=E8=A1=A8=20&?= =?UTF-8?q?=20=E8=AF=A6=E6=83=85=E9=A1=B5=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/ConsumerGroup/Detail.tsx | 249 +++++++++ .../src/pages/ConsumerGroup/ExpandedRow.tsx | 413 ++++++++++++++ .../pages/ConsumerGroup/ResetOffsetDrawer.tsx | 187 +++++++ .../src/pages/ConsumerGroup/config.tsx | 117 ++++ .../src/pages/ConsumerGroup/index.less | 186 +++++++ .../src/pages/ConsumerGroup/index.tsx | 134 +++++ .../pages/TopicDetail/ConsumerGroupDetail.tsx | 502 ++++++++++++++++++ .../src/pages/TopicDetail/ConsumerGroups.tsx | 152 +++--- .../pages/TopicDetail/ResetOffsetDrawer.tsx | 197 +++++++ .../src/pages/TopicDetail/index.tsx | 59 +- .../src/pages/pageRoutes.ts | 2 +- 11 files changed, 2102 insertions(+), 96 deletions(-) create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/Detail.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ResetOffsetDrawer.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/config.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.less create mode 100644 km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroupDetail.tsx create mode 100644 km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ResetOffsetDrawer.tsx diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/Detail.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/Detail.tsx new file mode 100644 index 00000000..34001fa0 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/Detail.tsx @@ -0,0 +1,249 @@ +import React, { useState, useEffect } from 'react'; +import { useParams, useHistory } from 'react-router-dom'; +import { Drawer, ProTable, Utils } from 'knowdesign'; +import { IconFont } from '@knowdesign/icons'; +import API from '@src/api/index'; +import { defaultPagination, hashDataParse } from '@src/constants/common'; +import { getGtoupTopicColumns } from './config'; +import { ExpandedRow } from './ExpandedRow'; +import ResetOffsetDrawer from './ResetOffsetDrawer'; +const { request } = Utils; + +export interface MetricLine { + createTime?: number; + metricPoints: Array<{ + aggType: string; + createTime: number; + timeStamp: number; + unit: string; + updateTime: number; + value: number; + }>; + name: string; + updateTime?: number; +} + +export interface MetricData { + metricLines?: Array; + metricLine?: MetricLine; + metricName: string; +} + +export interface HashData { + groupName: string; + topicName: string; +} + +const metricConsts = ['LogEndOffset', 'OffsetConsumed', 'Lag']; +const metricWithType = [ + { metricName: 'LogEndOffset', metricType: 104 }, + { metricName: 'OffsetConsumed', metricType: 102 }, + { metricName: 'Lag', metricType: 102 }, +]; + +const GroupDetail = (props: any) => { + const { scene } = props; + const urlParams = useParams<{ + clusterId: string; + }>(); + const now = Date.now(); + const history = useHistory(); + const [hashData, setHashData] = useState({ groupName: '', topicName: '' }); + const [visible, setVisible] = useState(false); + + const [topicData, setTopicData] = useState([]); + const [loading, setLoading] = useState(true); + const [pagination, setPagination] = useState(defaultPagination); + const [expandedData, setExpandedData] = useState([]); + const [chartData, setChartData] = useState>([]); + const [loadingObj, setLoadingObj] = useState({}); + const [timeRange, setTimeRange] = useState([now - 24 * 60 * 60 * 1000, now]); + const [curPartition, setCurPartition] = useState(''); + const [groupMetricsData, setGroupMetricsData] = useState>([]); + const [openKeys, setOpenKeys] = useState(); + const [resetOffsetVisible, setResetOffsetVisible] = useState(false); + const [resetOffsetArg, setResetOffsetArg] = useState({}); + + const genData = async ({ pageNo, pageSize, groupName }: any) => { + if (urlParams?.clusterId === undefined) return; + + setLoading(true); + const params = { + // searchKeywords: '', + pageNo, + pageSize, + }; + + request(API.getGroupTopicList(+urlParams?.clusterId, groupName), { params }) + .then((res: any) => { + setVisible(true); + setPagination({ + current: res.pagination?.pageNo, + pageSize: res.pagination?.pageSize, + total: res.pagination?.total, + }); + const newTopicListData = res?.bizData.map((item: any) => { + return { + ...item, + key: item.topicName, + }; + }); + setTopicData(newTopicListData || []); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + }; + + const onClose = () => { + setVisible(false); + // clean hash' + scene === 'topicDetail' && history.goBack(); + scene !== 'topicDetail' && window.history.pushState('', '', location.pathname); + }; + + const resetOffset = (record: any) => { + setResetOffsetVisible(true); + setResetOffsetArg({ + topicName: record?.topicName, + groupName: record?.groupName, + }); + }; + + const onTableChange = (pagination: any, filters: any, sorter: any) => { + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter, groupName: hashData.groupName }); + }; + + const onClickExpand = (expanded: any, record: any) => { + const key = record?.key; + // 之前展开过 + if (expandedData[key]?.length) return; + // 第一次展开 + setOpenKeys(key); + // const loading = { ...loadingObj }; + // loading[key] = true; + // setLoadingObj(loading); + // expanded && queryExpandedData(record, key); + }; + + useEffect(() => { + const hashData = hashDataParse(location.hash); + if (!hashData.groupName) { + setVisible(false); + } + setHashData(hashData); + // 获取分区列表 为图表模式做准备 + hashData.groupName && genData({ pageNo: 1, pageSize: pagination.pageSize, groupName: hashData.groupName }); + // getConsumersMetadata(hashData).then((res: any) => { + // if (res.exist) { + // setVisible(false); + // history.push(`/cluster/${params?.clusterId}/consumers`); + // return; + // } + // setVisible(true); + // getTopicGroupPartitionsHistory(hashData) + // .then((data: any) => { + // if (data.length > 0) { + // setCurPartition(data[0].partition); + // } + // setPartitionList(data); + // return data; + // }) + // .then((data) => { + // getTopicGroupMetricHistory(data, hashData); + // }) + // .catch((e) => { + // history.push(`/cluster/${params?.clusterId}/consumers`); + // setVisible(false); + // }); + // // 获取Consumer列表 表格模式 + // getTopicGroupMetric(hashData); + // }); + }, [hashDataParse(location.hash).groupName]); + + return ( + + // {global.hasPermission && + // global.hasPermission( + // scene === 'topicDetail' ? ClustersPermissionMap.TOPIC_RESET_OFFSET : ClustersPermissionMap.CONSUMERS_RESET_OFFSET + // ) && } + // + // + // } + > + ( + + ), + // expandedRowRender, + onExpand: onClickExpand, + columnWidth: '20px', + fixed: 'left', + expandIcon: ({ expanded, onExpand, record }: any) => { + return expanded ? ( + { + onExpand(record, e); + }} + /> + ) : ( + { + onExpand(record, e); + }} + /> + ); + }, + }, + style: { + width: '1032px', + }, + }, + }} + /> + + + ); +}; + +export default GroupDetail; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx new file mode 100644 index 00000000..fac1e99a --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ExpandedRow.tsx @@ -0,0 +1,413 @@ +import React, { useEffect, useState } from 'react'; +import { useParams, useHistory } from 'react-router-dom'; +import { ProTable, Utils, DRangeTime, Select, SingleChart } from 'knowdesign'; +import SwitchTab from '@src/components/SwitchTab'; +import { CHART_COLOR_LIST, getBasicChartConfig } from '@src/constants/chartConfig'; +import ContentWithCopy from '@src/components/CopyContent'; +import { IconFont } from '@knowdesign/icons'; +import API from '@src/api/index'; +import { hashDataParse } from '@src/constants/common'; +const { Option } = Select; + +export interface MetricLine { + createTime?: number; + metricPoints: Array<{ + aggType: string; + createTime: number; + timeStamp: number; + unit: string; + updateTime: number; + value: number; + }>; + name: string; + updateTime?: number; +} + +export interface MetricData { + metricLines?: Array; + metricLine?: MetricLine; + metricName: string; +} + +export interface HashData { + groupName: string; + topicName: string; +} + +const metricConsts = ['LogEndOffset', 'OffsetConsumed', 'Lag']; +const metricWithType = [ + { metricName: 'LogEndOffset', metricType: 104 }, + { metricName: 'OffsetConsumed', metricType: 102 }, + { metricName: 'Lag', metricType: 102 }, +]; + +export const ExpandedRow: any = ({ record, groupName }: any) => { + const params: any = useParams<{ + clusterId: string; + }>(); + const history = useHistory(); + const now = Date.now(); + const [allGroupMetricsData, setAllGroupMetricsData] = useState>([]); + const [showMode, setShowMode] = useState('table'); + const [curPartition, setCurPartition] = useState(''); + const [partitionList, setPartitionList] = useState([]); + const [timeRange, setTimeRange] = useState([now - 24 * 60 * 60 * 1000, now]); + const [consumerListLoading, setConsumerListLoading] = useState(true); + const [consumerList, setConsumerList] = useState([]); + const [groupMetricsData, setGroupMetricsData] = useState>([]); + const clusterId = Number(params.clusterId); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 5, + simple: true, + hideOnSinglePage: false, + }); + const columns = [ + { + title: 'Partition', + dataIndex: 'partitionId', + key: 'partitionId', + lineClampOne: true, + needTooltip: true, + width: 180, + }, + { + title: 'Member ID', + dataIndex: 'memberId', + key: 'memberId', + width: 200, + render: (v: string) => { + return v ? : '-'; + }, + }, + { + title: 'Current Offset', + dataIndex: 'OffsetConsumed', + key: 'OffsetConsumed', + render: (v: any, record: any) => { + return record?.latestMetrics?.metrics?.OffsetConsumed.toLocaleString(); + }, + sorter: true, + // sorter: { + // compare: (a: any, b: any) => { + // let value1 = a?.metrics?.find((item: any) => item.metricName === 'OffsetConsumed' && item.metricType === 102)?.metricValue + // let value2 = b?.metrics?.find((item: any) => item.metricName === 'OffsetConsumed' && item.metricType === 102)?.metricValue + // return value1 - value2 + // }, + // multiple: 1 + // } + }, + { + title: 'Log End Offset', + dataIndex: 'LogEndOffset', + key: 'LogEndOffset', + render: (v: any, record: any) => { + return record?.latestMetrics?.metrics?.LogEndOffset.toLocaleString(); + }, + sorter: true, + // sorter: { + // compare: (a: any, b: any) => { + // let value1 = a?.metrics?.find((item: any) => item.metricName === 'LogEndOffset' && item.metricType === 104)?.metricValue + // let value2 = b?.metrics?.find((item: any) => item.metricName === 'LogEndOffset' && item.metricType === 104)?.metricValue + // return value1 - value2 + // }, + // multiple: 2 + // } + }, + { + title: 'Lag', + dataIndex: 'Lag', + key: 'Lag', + render: (v: any, record: any) => { + return record?.latestMetrics?.metrics?.Lag.toLocaleString(); + }, + sorter: true, + // sorter: { + // compare: (a: any, b: any) => { + // let value1 = a?.metrics?.find((item: any) => item.metricName === 'Lag' && item.metricType === 102)?.metricValue + // let value2 = b?.metrics?.find((item: any) => item.metricName === 'Lag' && item.metricType === 102)?.metricValue + // return value1 - value2 + // }, + // multiple: 3 + // } + }, + { + title: 'Host', + dataIndex: 'host', + key: 'host', + }, + { + title: 'Client ID', + dataIndex: 'clientId', + key: 'clientId', + needTooltip: true, + lineClampOne: true, + width: 200, + }, + ]; + + const getTopicGroupMetric = ({ pagination = { current: 1, pageSize: 10 }, sorter = {} }: { pagination?: any; sorter?: any }) => { + setConsumerListLoading(true); + const params: any = { + // metricRealTimes: metricWithType, + latestMetricNames: metricConsts, + pageNo: pagination.current, + pageSize: pagination.pageSize, + sortField: sorter.field || undefined, + sortType: sorter.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : undefined, + }; + + return Utils.post( + API.getTopicGroupMetric({ + clusterId, + groupName: groupName, + topicName: record.topicName, + }), + params + ) + .then((data: any) => { + if (!data) return; + setPagination((origin: any) => { + return { ...origin, current: data.pagination?.pageNo, pageSize: data.pagination?.pageSize }; + }); + setConsumerList(data?.bizData || []); + }) + .finally(() => { + setConsumerListLoading(false); + }); + }; + + const getTopicGroupMetricHistory = (partitions: Array) => { + const params = { + aggType: 'sum', + groupTopics: partitions.map((p: any) => ({ + partition: p.value, + topic: record.topicName, + })), + group: groupName, + metricsNames: metricWithType.map((item) => item.metricName), + startTime: timeRange[0], + endTime: timeRange[1], + topNu: 0, + }; + Utils.post(API.getTopicGroupMetricHistory(clusterId), params).then((data: Array) => { + // ! 替换接口返回 + setAllGroupMetricsData(data); + }); + }; + + const getConsumersMetadata = (hashData: HashData) => { + return Utils.request(API.getConsumersMetadata(params.clusterId, groupName, record.topicName)); + }; + + const getTopicsMetaData = () => { + return Utils.request(API.getTopicsMetaData(record?.topicName, +params.clusterId)); + }; + const onTableChange = (pagination: any, filters: any, sorter: any) => { + getTopicGroupMetric({ pagination, sorter }); + }; + + // useEffect(() => { + // getTopicGroupMetric(); + // }, [sortObj]); + + useEffect(() => { + const hashData = hashDataParse(location.hash); + // if (!hashData.groupName) return; + // 获取分区列表 为图表模式做准备 + + getConsumersMetadata(hashData).then((res: any) => { + if (!res.exist) { + history.push(`/cluster/${params?.clusterId}/consumers`); + return; + } + getTopicsMetaData() + .then((data: any) => { + const partitionLists = (data?.partitionIdList || []).map((item: any) => { + return { + label: item, + value: item, + }; + }); + setCurPartition(partitionLists?.[0]?.value); + setPartitionList(partitionLists); + getTopicGroupMetricHistory(partitionLists); + }) + .catch((e) => { + history.push(`/cluster/${params?.clusterId}/consumers`); + }); + // 获取Consumer列表 表格模式 + getTopicGroupMetric({}); + }); + }, [hashDataParse(location.hash).groupName]); + + useEffect(() => { + if (partitionList.length === 0) return; + getTopicGroupMetricHistory(partitionList); + }, [timeRange]); + + useEffect(() => { + if (curPartition === '' || allGroupMetricsData.length === 0) return; + const filteredData = allGroupMetricsData.map((item) => { + const allData = item.metricLines.reduce( + (acc, cur) => { + if (acc.metricLine.metricPoints.length === 0) { + acc.metricLine.metricPoints = cur.metricPoints.map((p: any) => ({ + timeStamp: p.timeStamp, + value: Number(p.value), + })); + } else { + acc.metricLine.metricPoints.forEach((mp) => { + const curMetricPoint = cur.metricPoints.find((curmp) => curmp.timeStamp === mp.timeStamp); + mp.value += curMetricPoint ? Number(curMetricPoint.value) : 0; + }); + } + return acc; + }, + { + metricName: item.metricName, + metricLine: { + name: 'all', + metricPoints: [], + }, + } + ); + return curPartition === '__all__' + ? allData + : { + metricName: item.metricName, + metricLine: item.metricLines.find((line) => line.name.indexOf(curPartition) >= 0), + }; + }); + setGroupMetricsData(filteredData); + }, [curPartition, allGroupMetricsData]); + + return ( +
+
+
+
+
+ {showMode === 'chart' && ( + + )} + {showMode === 'chart' && ( + { + setTimeRange(o); + }} + > + )} + {showMode === 'chart' &&
} + setShowMode(key)}> + +
+ +
+
+ +
+ +
+
+
+
+
+ {showMode === 'table' && ( + + )} + {showMode === 'chart' && ( +
+ { + return data.map((metricData: any) => { + const partitionMetricData = metricData.metricLine?.metricPoints || []; + return { + name: metricData.metricName, + data: partitionMetricData.map((item: any) => [item.timeStamp, item.value, item.unit]), + lineStyle: { + width: 1.5, + }, + smooth: 0.25, + symbol: 'emptyCircle', + symbolSize: 4, + emphasis: { + disabled: true, + }, + }; + }); + }} + /> +
+ )} +
+
+ ); +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ResetOffsetDrawer.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ResetOffsetDrawer.tsx new file mode 100644 index 00000000..b03f6832 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/ResetOffsetDrawer.tsx @@ -0,0 +1,187 @@ +import React, { useState, useEffect } from 'react'; +import { Button, DatePicker, Drawer, Form, notification, Radio, Utils, Space, Divider, message } from 'knowdesign'; +import { useParams } from 'react-router-dom'; +import EditTable from '../TestingProduce/component/EditTable'; +import Api from '@src/api/index'; +import moment from 'moment'; + +const CustomSelectResetTime = (props: { value?: string; onChange?: (val: Number | String) => void }) => { + const { value, onChange } = props; + const [timeSetMode, setTimeSetMode] = useState('newest'); + useEffect(() => { + onChange('newest'); + }, []); + return ( + <> + { + setTimeSetMode(e.target.value); + if (e.target.value === 'newest') { + onChange('newest'); + } + }} + value={timeSetMode} + > + 最新Offset + 自定义 + + {timeSetMode === 'custom' && ( + { + onChange(v.valueOf()); + }} + > + )} + + ); +}; + +export default (props: any) => { + const { record, visible, setVisible } = props; + const routeParams = useParams<{ + clusterId: string; + }>(); + const [form] = Form.useForm(); + const defaultResetType = 'assignedTime'; + const [resetType, setResetType] = useState(defaultResetType); + const customFormRef: any = React.createRef(); + const clusterPhyId = Number(routeParams.clusterId); + const [partitionIdList, setPartitionIdList] = useState([]); + useEffect(() => { + form.setFieldsValue({ + resetType: defaultResetType, + }); + }, []); + + useEffect(() => { + visible && + Utils.request(Api.getTopicsMetaData(record?.topicName, +routeParams.clusterId)) + .then((res: any) => { + const partitionLists = (res?.partitionIdList || []).map((item: any) => { + return { + label: item, + value: item, + }; + }); + setPartitionIdList(partitionLists); + }) + .catch((err) => { + message.error(err); + }); + }, [visible]); + const confirm = () => { + let tableData; + if (customFormRef.current) { + tableData = customFormRef.current.getTableData(); + } + const formData = form.getFieldsValue(); + let resetParams: any = { + clusterId: clusterPhyId, + createIfNotExist: false, + groupName: record.groupName, + topicName: record.topicName, + }; + if (formData.resetType === 'assignedTime') { + resetParams.resetType = formData.timestamp === 'newest' ? 0 : 2; + if (resetParams.resetType === 2) { + resetParams.timestamp = formData.timestamp; + } + } + if (formData.resetType === 'partition') { + resetParams.resetType = 3; + resetParams.offsetList = tableData + ? tableData.map((item: { key: string; value: string }) => ({ partitionId: item.key, offset: item.value })) + : []; + } + Utils.put(Api.resetGroupOffset(), resetParams).then((data) => { + if (data === null) { + notification.success({ + message: '重置offset成功', + }); + setVisible(false); + } else { + notification.error({ + message: '重置offset失败', + }); + setVisible(false); + } + }); + }; + return ( + <> + + + + + + } + className="cluster-detail-consumer-resetoffset" + onClose={(_) => { + setVisible(false); + }} + > +
+ + { + setResetType(e.target.value); + }} + > + 重置到指定时间 + 重置分区 + + + {resetType === 'assignedTime' && ( + + + + )} + {resetType === 'partition' && ( + + + + )} +
+
+ + ); +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/config.tsx new file mode 100644 index 00000000..8edd9280 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/config.tsx @@ -0,0 +1,117 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import React from 'react'; +import { AppContainer } from 'knowdesign'; +import TagsWithHide from '@src/components/TagsWithHide'; +import { ClustersPermissionMap } from '../CommonConfig'; + +export const runningStatusEnum: any = { + 1: 'Doing', + 2: 'Prepare', + 3: 'Success', + 4: 'Failed', + 5: 'Canceled', +}; + +export const defaultPagination = { + current: 1, + pageSize: 10, + position: 'bottomRight', + showSizeChanger: true, + pageSizeOptions: ['10', '20', '50', '100', '200', '500'], +}; + +export const getGroupColumns = (arg?: any) => { + const columns = [ + { + title: 'ConsumerGroup', + dataIndex: 'name', + key: 'name', + lineClampTwo: true, + render: (v: any, r: any) => { + return ( + { + window.location.hash = `groupName=${v || ''}`; + }} + > + {v} + + ); + }, + width: 200, + }, + { + title: '消费的Topic', + dataIndex: 'topicNameList', + key: 'topicNameList', + width: 200, + render(t: any, r: any) { + return t && t.length > 0 ? `共有${num}个`} /> : '-'; + }, + }, + { + title: 'Status', + dataIndex: 'state', + key: 'state', + width: 200, + }, + { + title: 'Member数', + dataIndex: 'memberCount', + key: 'memberCount', + width: 200, + render: (t: number) => (t ? t.toLocaleString() : '-'), + }, + ]; + return columns; +}; + +export const getGtoupTopicColumns = (arg?: any) => { + const [global] = AppContainer.useGlobalValue(); + const columns: any = [ + { + title: 'Topic名称', + dataIndex: 'topicName', + key: 'topicName', + needTooltip: true, + lineClampOne: true, + width: 150, + }, + { + title: 'Status', + dataIndex: 'state', + key: 'state', + width: 150, + }, + { + title: 'Max Lag', + dataIndex: 'maxLag', + key: 'maxLag', + width: 150, + render: (t: number) => (t ? t.toLocaleString() : '-'), + }, + { + title: 'Member数', + dataIndex: 'memberCount', + key: 'memberCount', + width: 150, + render: (t: number) => (t ? t.toLocaleString() : '-'), + }, + ]; + if (global.hasPermission && global.hasPermission(ClustersPermissionMap.CONSUMERS_RESET_OFFSET)) { + columns.push({ + title: '操作', + dataIndex: 'desc', + key: 'desc', + width: 150, + render: (value: any, record: any) => { + return ( + + ); + }, + }); + } + return columns; +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.less b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.less new file mode 100644 index 00000000..82c5bcf5 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.less @@ -0,0 +1,186 @@ +.brokerList { + .d-table-box-header { + padding: 0 0 12px 0; + } + .tag-success, + .tag-error { + padding: 2px 8px; + border-radius: 4px; + margin-left: 4px; + } + .tag-success { + background: #f1faf2; + color: #73d380; + } + .tag-error { + background: #fff0ef; + color: #ff7066; + } +} + +.operating-state { + .consumers-search { + display: contents; + .search-input-short { + margin-right: 8px; + } + } + .pro-table-wrap { + // padding: 17px 24px; + background: #fff; + border-radius: 12px; + } +} +.consumer-group-detail-drawer { + &-table { + .dcloud-table-cell { + padding: 7px 16px 8px 2px !important; + } + .dcloud-table-row-expand-icon-cell { + padding: 7px 7px 5px 24px !important; + } + + .dcloud-table-expanded-row-fixed { + padding: 16px 20px !important; + .dcloud-table-cell { + font-size: 12px !important; + } + } + } + + .dcloud-drawer-extra { + button { + width: 90px; + } + .divider { + width: 1px; + height: 17px; + margin: 0 16px; + background: rgba(206, 212, 218, 0.6); + } + } + .dcloud-drawer-body { + padding-top: 2px !important; + } +} + +.consumer-group-detail { + // border: 1px solid #EFF2F7; + // border-radius: 8px; + // padding: 12px 16px; + .title-and-mode { + display: flex; + align-items: center; + justify-content: space-between; + height: 60px; + &-header { + font-family: @font-family-bold; + font-size: 13px; + } + h4 { + font-family: PingFangSC-Semibold; + } + .right { + display: flex; + justify-content: center; + align-items: center; + .d-range-time-input { + height: 27px !important; + padding: 0 11px; + input { + line-height: 100%; + } + } + .divider { + width: 1px; + height: 20px; + background: rgba(206, 212, 218, 0.6); + margin: 0 8px; + } + .switch-mode { + .dcloud-radio-button-wrapper { + font-size: 14px; + width: 34px; + height: 23px; + padding: 0; + line-height: 22px; + text-align: center; + } + } + } + } + .single-chart, + .table { + background: #f8f9fa; + // margin-top: 12px; + border-radius: 8px; + .dcloud-table { + height: 210px; + overflow: auto; + background-color: transparent; + .dcloud-table-content .dcloud-table-cell { + background-color: transparent; + } + } + .dcloud-pagination { + height: 32px; + margin-bottom: 0; + margin-top: 8px; + } + } + .single-chart { + padding: 16px 22px 4px; + .single-chart-header { + display: none; + } + } + .content-with-copy { + display: flex; + align-items: center; + .content { + flex: 1; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-break: break-all; + } + .copy-icon { + width: 20px; + height: 20px; + padding-top: 2px; + border-radius: 50%; + margin-left: 4px; + font-size: 16px; + color: #adb5bc; + opacity: 0; + &:hover { + background: rgba(33, 37, 41, 0.04); + color: #74788d; + } + } + } + .dcloud-table-cell-row-hover { + .copy-icon { + opacity: 1; + } + } +} +.cluster-detail-consumer-resetoffset { + .reset-offset-form { + .dcloud-radio-wrapper { + width: 154px; + } + } + .operate-wrap { + display: flex; + justify-content: center; + align-items: center; + .operate-btns-divider { + width: 1px; + height: 17px; + background: rgba(0, 0, 0, 0.13); + margin: 0 16px; + } + } +} diff --git a/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx new file mode 100644 index 00000000..55647ae0 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/ConsumerGroup/index.tsx @@ -0,0 +1,134 @@ +import React, { useState, useEffect, memo } from 'react'; +import { useParams } from 'react-router-dom'; +import { ProTable, Utils, AppContainer, SearchInput, Divider, Form, Input, Button } from 'knowdesign'; +import { IconFont } from '@knowdesign/icons'; +import API from '../../api'; +import { getGroupColumns, defaultPagination } from './config'; +import { tableHeaderPrefix } from '@src/constants/common'; +import BrokerDetail from '../BrokerDetail'; +import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb'; +import ConsumerGroupHealthCheck from '@src/components/CardBar/ConsumerGroupHealthCheck'; +import GroupDetail from './Detail'; +import './index.less'; + +const { request } = Utils; + +const BrokerList: React.FC = (props: any) => { + const [global] = AppContainer.useGlobalValue(); + const [form] = Form.useForm(); + const urlParams = useParams(); // 获取地址栏参数 + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [searchKeywords, setSearchKeywords] = useState(''); + const [pagination, setPagination] = useState(defaultPagination); + const [searchWords, setSearchWords] = useState<{ groupName: string; topicName: string }>({ groupName: '', topicName: '' }); + // 请求接口获取数据 + const genData = async ({ pageNo, pageSize }: any) => { + if (urlParams?.clusterId === undefined) return; + + setLoading(true); + const params = { + searchGroupName: searchWords.groupName ? searchWords.groupName.slice(0, 128) : undefined, + searchTopicName: searchWords.topicName ? searchWords.topicName.slice(0, 128) : undefined, + pageNo, + pageSize, + }; + + request(API.getOperatingStateList(urlParams?.clusterId), { params }) + .then((res: any) => { + setPagination({ + current: res.pagination?.pageNo, + pageSize: res.pagination?.pageSize, + total: res.pagination?.total, + }); + setData(res?.bizData || []); + setLoading(false); + }) + .catch((err) => { + setLoading(false); + }); + }; + + // 查询 + const onFinish = (formData: any) => { + setSearchWords(formData); + }; + + const onTableChange = (pagination: any, filters: any, sorter: any) => { + genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter }); + }; + + useEffect(() => { + genData({ + pageNo: 1, + pageSize: pagination.pageSize, + }); + }, [searchWords]); + + return ( +
+
+ +
+
+ +
+
+
+
+
genData({ pageNo: pagination.current, pageSize: pagination.pageSize })} + > + +
+ +
+ + + + + + +
+
+
+ + + +
+
+
+
+ +
+ {} +
+ ); +}; + +export default BrokerList; diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroupDetail.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroupDetail.tsx new file mode 100644 index 00000000..bd502b1a --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroupDetail.tsx @@ -0,0 +1,502 @@ +import React, { useState, useEffect } from 'react'; +import { useParams, useHistory } from 'react-router-dom'; +import { AppContainer, Divider, Drawer, ProTable, Select, SingleChart, Space, Tooltip, Utils } from 'knowdesign'; +import { IconFont } from '@knowdesign/icons'; +import { DRangeTime } from 'knowdesign'; +import { CHART_COLOR_LIST, getBasicChartConfig } from '@src/constants/chartConfig'; +import Api from '@src/api/index'; +import { hashDataParse } from '@src/constants/common'; +import { ClustersPermissionMap } from '../CommonConfig'; +import ResetOffsetDrawer from './ResetOffsetDrawer'; +import SwitchTab from '@src/components/SwitchTab'; +import ContentWithCopy from '@src/components/CopyContent'; + +const { Option } = Select; + +export interface MetricLine { + createTime?: number; + metricPoints: Array<{ + aggType: string; + createTime: number; + timeStamp: number; + unit: string; + updateTime: number; + value: number; + }>; + name: string; + updateTime?: number; +} +export interface MetricData { + metricLines?: Array; + metricLine?: MetricLine; + metricName: string; +} + +export interface HashData { + groupName: string; + topicName: string; +} + +const metricConsts = ['LogEndOffset', 'OffsetConsumed', 'Lag']; +const metricWithType = [ + { metricName: 'LogEndOffset', metricType: 104 }, + { metricName: 'OffsetConsumed', metricType: 102 }, + { metricName: 'Lag', metricType: 102 }, +]; + +export default (props: any) => { + const { scene, visible, setVisible, hashData } = props; + const params = useParams<{ + clusterId: string; + }>(); + const history = useHistory(); + const [global] = AppContainer.useGlobalValue(); + // const { record } = props; + const now = Date.now(); + const [allGroupMetricsData, setAllGroupMetricsData] = useState>([]); + const [groupMetricsData, setGroupMetricsData] = useState>([]); + const [timeRange, setTimeRange] = useState([now - 24 * 60 * 60 * 1000, now]); + const [consumerList, setConsumerList] = useState([]); + const [partitionList, setPartitionList] = useState([]); + const [curPartition, setCurPartition] = useState(''); + const [showMode, setShowMode] = useState('table'); + const [pageIndex, setPageIndex] = useState(1); + const [pageTotal, setPageTotal] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [consumerListLoading, setConsumerListLoading] = useState(true); + const [consumerChartLoading, setConsumerChartLoading] = useState(false); + // const [hashData, setHashData] = useState({ groupName: '', topicName: '' }); + // const [visible, setVisible] = useState(false); + const [sortObj, setSortObj] = useState<{ + sortField: string; + sortType: 'desc' | 'asc' | ''; + }>({ sortField: '', sortType: '' }); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + position: 'bottomRight', + showSizeChanger: true, + pageSizeOptions: ['10', '20', '50', '100', '200', '500'], + showTotal: (total: number) => `共 ${total} 条目`, + }); + const clusterId = Number(params.clusterId); + const columns = [ + { + title: 'Topic Partition', + dataIndex: 'partitionId', + key: 'partitionId', + lineClampOne: true, + needTooltip: true, + width: 180, + render: (v: string, record: any) => { + return `${record.topicName}-${v}`; + }, + }, + { + title: 'Member ID', + dataIndex: 'memberId', + key: 'memberId', + width: 200, + render: (v: string) => { + return v ? : '-'; + }, + }, + { + title: 'Current Offset', + dataIndex: 'OffsetConsumed', + key: 'OffsetConsumed', + render: (v: any, record: any) => { + return record?.latestMetrics?.metrics?.OffsetConsumed.toLocaleString(); + }, + sorter: true, + // sorter: { + // compare: (a: any, b: any) => { + // let value1 = a?.metrics?.find((item: any) => item.metricName === 'OffsetConsumed' && item.metricType === 102)?.metricValue + // let value2 = b?.metrics?.find((item: any) => item.metricName === 'OffsetConsumed' && item.metricType === 102)?.metricValue + // return value1 - value2 + // }, + // multiple: 1 + // } + }, + { + title: 'Log End Offset', + dataIndex: 'LogEndOffset', + key: 'LogEndOffset', + render: (v: any, record: any) => { + return record?.latestMetrics?.metrics?.LogEndOffset.toLocaleString(); + }, + sorter: true, + // sorter: { + // compare: (a: any, b: any) => { + // let value1 = a?.metrics?.find((item: any) => item.metricName === 'LogEndOffset' && item.metricType === 104)?.metricValue + // let value2 = b?.metrics?.find((item: any) => item.metricName === 'LogEndOffset' && item.metricType === 104)?.metricValue + // return value1 - value2 + // }, + // multiple: 2 + // } + }, + { + title: 'Lag', + dataIndex: 'Lag', + key: 'Lag', + render: (v: any, record: any) => { + return record?.latestMetrics?.metrics?.Lag.toLocaleString(); + }, + sorter: true, + // sorter: { + // compare: (a: any, b: any) => { + // let value1 = a?.metrics?.find((item: any) => item.metricName === 'Lag' && item.metricType === 102)?.metricValue + // let value2 = b?.metrics?.find((item: any) => item.metricName === 'Lag' && item.metricType === 102)?.metricValue + // return value1 - value2 + // }, + // multiple: 3 + // } + }, + { + title: 'Host', + dataIndex: 'host', + key: 'host', + }, + { + title: 'Client ID', + dataIndex: 'clientId', + key: 'clientId', + needTooltip: true, + lineClampOne: true, + width: 200, + }, + ]; + const getTopicGroupMetric = ({ + hashData, + pagination = { current: 1, pageSize: 10 }, + sorter = {}, + }: { + hashData: HashData; + pagination?: any; + sorter?: any; + }) => { + setConsumerListLoading(true); + const params: any = { + // metricRealTimes: metricWithType, + latestMetricNames: metricConsts, + pageNo: pagination.current, + pageSize: pagination.pageSize, + sortField: sorter.field || undefined, + sortType: sorter.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : undefined, + }; + // if (sorter.sortField && sorter.sortType) { + // params.sortField = sorter.sortField; + // params.sortType = sorter.sortType; + // } + return Utils.post( + Api.getTopicGroupMetric({ + clusterId, + groupName: hashData.groupName, + topicName: hashData.topicName, + }), + params + ) + .then((data: any) => { + if (!data) return; + + setPagination({ + current: data.pagination?.pageNo, + pageSize: data.pagination?.pageSize, + total: data.pagination?.total, + }); + setConsumerList(data?.bizData); + }) + .finally(() => { + setConsumerListLoading(false); + }); + }; + const getTopicGroupPartitionsHistory = (hashData: HashData) => { + return Utils.request(Api.getTopicGroupPartitionsHistory(clusterId, hashData.groupName), { + params: { + startTime: timeRange[0], + endTime: timeRange[1], + }, + }); + }; + const getTopicGroupMetricHistory = (partitions: Array, hashData: HashData) => { + const params = { + aggType: 'sum', + groupTopics: partitions?.map((p) => ({ + partition: p.value, + topic: hashData.topicName, + })), + group: hashData.groupName, + metricsNames: metricWithType.map((item) => item.metricName), + startTime: timeRange[0], + endTime: timeRange[1], + topNu: 0, + }; + Utils.post(Api.getTopicGroupMetricHistory(clusterId), params).then((data: Array) => { + setAllGroupMetricsData(data); + }); + }; + const getConsumersMetadata = (hashData: HashData) => { + return Utils.request(Api.getConsumersMetadata(clusterId, hashData.groupName, hashData.topicName)); + }; + + const getTopicsMetaData = (hashData: HashData) => { + return Utils.request(Api.getTopicsMetaData(hashData.topicName, clusterId)); + }; + + const onClose = () => { + setVisible(false); + setSortObj({ + sortField: '', + sortType: '', + }); + // clean hash' + // scene === 'topicDetail' && history.goBack(); + // scene !== 'topicDetail' && window.history.pushState('', '', location.pathname); + }; + + const onTableChange = (pagination: any, filters: any, sorter: any) => { + getTopicGroupMetric({ hashData, pagination, sorter }); + // setPageIndex(pagination.current); + }; + + useEffect(() => { + if (curPartition === '' || allGroupMetricsData.length === 0) return; + const filteredData = allGroupMetricsData.map((item) => { + const allData = item.metricLines.reduce( + (acc, cur) => { + if (acc.metricLine.metricPoints.length === 0) { + acc.metricLine.metricPoints = cur.metricPoints.map((p) => ({ + timeStamp: p.timeStamp, + value: Number(p.value), + })); + } else { + acc.metricLine.metricPoints.forEach((mp) => { + const curMetricPoint = cur.metricPoints.find((curmp) => curmp.timeStamp === mp.timeStamp); + mp.value += curMetricPoint ? Number(curMetricPoint.value) : 0; + }); + } + return acc; + }, + { + metricName: item.metricName, + metricLine: { + name: 'all', + metricPoints: [], + }, + } + ); + return curPartition === '__all__' + ? allData + : { + metricName: item.metricName, + metricLine: item.metricLines.find((line) => line.name.indexOf(curPartition) >= 0), + }; + }); + setGroupMetricsData(filteredData); + }, [curPartition, allGroupMetricsData]); + + useEffect(() => { + // const hashData = hashDataParse(location.hash); + if (!hashData.groupName || !hashData.topicName) return; + // setHashData(hashData); + // 获取分区列表 为图表模式做准备 + visible && + getConsumersMetadata(hashData).then((res: any) => { + if (!res.exist) { + setVisible(false); + // history.push(`/cluster/${params?.clusterId}/consumers`); + return; + } + getTopicsMetaData(hashData) + // .then((data: any) => { + // if (data.length > 0) { + // setCurPartition(data[0].partition); + // } + // setPartitionList(data); + // return data; + // }) + .then((data: any) => { + const partitionLists = (data?.partitionIdList || []).map((item: any) => { + return { + label: item, + value: item, + }; + }); + setCurPartition(partitionLists?.[0]?.value); + setPartitionList(partitionLists); + getTopicGroupMetricHistory(partitionLists, hashData); + }) + .catch((e) => { + // history.push(`/cluster/${params?.clusterId}/consumers`); + setVisible(false); + }); + // 获取Consumer列表 表格模式 + getTopicGroupMetric({ hashData: hashData as HashData }); + }); + }, [visible]); + + useEffect(() => { + if (partitionList.length === 0) return; + getTopicGroupMetricHistory(partitionList, hashData); + }, [timeRange]); + + return ( + + {global.hasPermission && + global.hasPermission( + scene === 'topicDetail' ? ClustersPermissionMap.TOPIC_RESET_OFFSET : ClustersPermissionMap.CONSUMERS_RESET_OFFSET + ) && } + + + } + > +
+
+
+
+ {showMode === 'chart' && ( + + )} + {showMode === 'chart' && ( + { + setTimeRange(o); + }} + > + )} + {showMode === 'chart' &&
} + setShowMode(key)}> + +
+ +
+
+ +
+ +
+
+
+
+
+ {showMode === 'table' && ( + // { + // setSortObj({ + // sortField: sorter.field || '', + // sortType: sorter.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : '', + // }); + // setPageIndex(pagination.current); + // }} + // >
+ + )} + {showMode === 'chart' && ( +
+ { + return data.map((metricData: any) => { + const partitionMetricData = metricData.metricLine?.metricPoints || []; + return { + name: metricData.metricName, + data: partitionMetricData.map((item: any) => [item.timeStamp, item.value, item.unit]), + lineStyle: { + width: 1.5, + }, + smooth: 0.25, + symbol: 'emptyCircle', + symbolSize: 4, + emphasis: { + disabled: true, + }, + }; + }); + }} + /> +
+ )} +
+
+ ); +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroups.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroups.tsx index 9fa3be5c..9d1c6a6d 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroups.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConsumerGroups.tsx @@ -1,50 +1,58 @@ import React, { useState, useEffect } from 'react'; import { ProTable, Utils } from 'knowdesign'; import Api from '@src/api'; +import { useParams } from 'react-router-dom'; +import ConsumerGroupDetail from './ConsumerGroupDetail'; const { request } = Utils; -const getColmns = (solveClick: any) => { - const columns = [ + +const getColmns = (arg: any) => { + const baseColumns: any = [ { title: 'ConsumerGroup', - dataIndex: 'ConsumerGroup', - key: 'ConsumerGroup', + dataIndex: 'groupName', + key: 'groupName', + render: (v: any, r: any) => { + return arg.getGroupInfo(v)}>{v}; + }, }, { - title: '关联KafkaUser', - dataIndex: 'kafkaUser', - key: 'kafkaUser', + title: '消费的Topic', + dataIndex: 'topicName', + key: 'topicName', }, + // { + // title: 'Principle', + // dataIndex: 'kafkaUser', + // key: 'kafkaUser', + // }, { title: 'Status', - dataIndex: 'status', - key: 'status', + dataIndex: 'state', + key: 'state', }, { title: 'Max Lag', dataIndex: 'maxLag', key: 'maxLag', + render: (t: number) => (t ? t.toLocaleString() : '-'), }, { title: 'Member数', - dataIndex: 'member', - key: 'member', - }, - { - title: '操作', - dataIndex: 'option', - key: 'option', - // eslint-disable-next-line react/display-name - render: (_t: any, r: any) => { - return solveClick(r)}>解决; - }, + dataIndex: 'memberCount', + key: 'memberCount', + render: (t: number) => (t ? t.toLocaleString() : '-'), }, ]; - return columns; + + return baseColumns; }; const TopicGroup = (props: any) => { + const { hashData } = props; + const urlParams = useParams(); // 获取地址栏参数 const [loading, setLoading] = useState(false); const [data, setData] = useState([]); + const [visible, setVisible] = useState(false); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, @@ -52,32 +60,22 @@ const TopicGroup = (props: any) => { showSizeChanger: true, pageSizeOptions: ['10', '20', '50', '100', '200', '500'], showTotal: (total: number) => `共 ${total} 条目`, - // locale: { - // items_per_page: '条', - // }, - // selectComponentClass: CustomSelect, }); - const solveClick = (record: any) => {}; + const [groupName, setGroupName] = useState(''); // 请求接口获取数据 const genData = async ({ pageNo, pageSize, filters = null, sorter = null }: any) => { - // if (clusterId === undefined) return; - - // filters = filters || filteredInfo; + if (urlParams?.clusterId === undefined || hashData?.topicName === undefined) return; setLoading(true); - // const params = dealTableRequestParams({ searchKeywords, pageNo, pageSize, sorter, filters, isPhyId: true }); const params = { - filterKey: 'string', - filterPartitionId: 1, - filterValue: 'string', - maxRecords: 100, - pullTimeoutUnitMs: 10000, - truncate: true, + searchGroupName: props.searchKeywords ? props.searchKeywords.slice(0, 128) : undefined, + pageNo, + pageSize, }; - request(Api.getTopicMessagesList('你好', 2), { params }) + request(Api.getTopicGroupList(hashData?.topicName, urlParams?.clusterId), { params }) .then((res: any) => { setPagination({ current: res.pagination?.pageNo, @@ -97,38 +95,62 @@ const TopicGroup = (props: any) => { }); }; - const onTableChange = (pagination: any, filters: any, sorter: any) => { - setPagination(pagination); - // const asc = sorter?.order && sorter?.order === 'ascend' ? true : false; - // const sortColumn = sorter.field && toLine(sorter.field); - // genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, asc, sortColumn, queryTerm: searchResult, ...allParams }); + const getGroupInfo = (groupName: string) => { + setVisible(true); + setGroupName(groupName); }; - // useEffect(() => { - // genData({ - // pageNo: 1, - // pageSize: pagination.pageSize, - // // sorter: defaultSorter - // }); - // }, [props]); + const onTableChange = (pagination: any, filters: any, sorter: any) => { + // setPagination(pagination); + // const asc = sorter?.order && sorter?.order === 'ascend' ? true : false; + // const sortColumn = sorter.field && toLine(sorter.field); + genData({ pageNo: pagination.current, pageSize: pagination.pageSize }); + }; + + useEffect(() => { + props.positionType === 'ConsumerGroups' && + genData({ + pageNo: 1, + pageSize: pagination.pageSize, + // sorter: defaultSorter + }); + }, [props.searchKeywords]); return ( - +
+ + {} +
+ // ); }; diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ResetOffsetDrawer.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ResetOffsetDrawer.tsx new file mode 100644 index 00000000..c079393b --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ResetOffsetDrawer.tsx @@ -0,0 +1,197 @@ +import React, { useState, useEffect } from 'react'; +import { Button, DatePicker, Drawer, Form, notification, Radio, Utils, Space, Divider, message } from 'knowdesign'; +import { useParams } from 'react-router-dom'; +import EditTable from '../TestingProduce/component/EditTable'; +import Api from '@src/api/index'; +import moment from 'moment'; + +const CustomSelectResetTime = (props: { value?: string; onChange?: (val: Number | String) => void }) => { + const { value, onChange } = props; + const [timeSetMode, setTimeSetMode] = useState('newest'); + useEffect(() => { + onChange('newest'); + }, []); + return ( + <> + { + setTimeSetMode(e.target.value); + if (e.target.value === 'newest') { + onChange('newest'); + } + }} + value={timeSetMode} + > + 最新Offset + 自定义 + + {timeSetMode === 'custom' && ( + { + onChange(v.valueOf()); + }} + > + )} + + ); +}; + +export default (props: any) => { + const { record } = props; + const routeParams = useParams<{ + clusterId: string; + }>(); + const [form] = Form.useForm(); + const defaultResetType = 'assignedTime'; + const [resetType, setResetType] = useState(defaultResetType); + const [resetOffsetVisible, setResetOffsetVisible] = useState(false); + const customFormRef: any = React.createRef(); + const clusterPhyId = Number(routeParams.clusterId); + const [partitionIdList, setPartitionIdList] = useState([]); + useEffect(() => { + form.setFieldsValue({ + resetType: defaultResetType, + }); + }, []); + + useEffect(() => { + Utils.request(Api.getTopicsMetaData(record?.topicName, +routeParams.clusterId)) + .then((res: any) => { + const partitionLists = (res?.partitionIdList || []).map((item: any) => { + return { + label: item, + value: item, + }; + }); + setPartitionIdList(partitionLists); + }) + .catch((err) => { + message.error(err); + }); + }, []); + const confirm = () => { + let tableData; + if (customFormRef.current) { + tableData = customFormRef.current.getTableData(); + } + const formData = form.getFieldsValue(); + let resetParams: any = { + clusterId: clusterPhyId, + createIfNotExist: false, + groupName: record.groupName, + topicName: record.topicName, + }; + if (formData.resetType === 'assignedTime') { + resetParams.resetType = formData.timestamp === 'newest' ? 0 : 2; + if (resetParams.resetType === 2) { + resetParams.timestamp = formData.timestamp; + } + } + if (formData.resetType === 'partition') { + resetParams.resetType = 3; + resetParams.offsetList = tableData + ? tableData.map((item: { key: string; value: string }) => ({ partitionId: item.key, offset: item.value })) + : []; + } + Utils.put(Api.resetGroupOffset(), resetParams).then((data) => { + if (data === null) { + notification.success({ + message: '重置offset成功', + }); + setResetOffsetVisible(false); + } else { + notification.error({ + message: '重置offset失败', + }); + setResetOffsetVisible(false); + } + }); + }; + return ( + <> + + + + + + + + } + className="cluster-detail-consumer-resetoffset" + onClose={(_) => { + setResetOffsetVisible(false); + }} + > +
+ + { + setResetType(e.target.value); + }} + > + 重置到指定时间 + 重置分区 + + + {resetType === 'assignedTime' && ( + + + + )} + {resetType === 'partition' && ( + + + + )} +
+
+ + ); +}; diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx index cd2d6d66..bc3f6ed4 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/index.tsx @@ -1,13 +1,16 @@ import React, { useState, useEffect } from 'react'; import { useHistory, useParams } from 'react-router-dom'; -import { Tabs, Utils, Drawer, Tag, AppContainer, SearchInput, notification } from 'knowdesign'; +import { Tabs, Utils, Drawer, Tag, AppContainer, SearchInput } from 'knowdesign'; +import notification from '@src/components/Notification'; + import Api from '@src/api'; import BrokersDetail from './BrokersDetail'; import Messages from './Messages'; import ConsumerGroups from './ConsumerGroups'; import ACLs from './ACLs'; import Configuration from './Configuration'; -import Consumers from '@src/pages/Consumers'; +import Consumers from './ConsumerGroups'; +// import Consumers from '@src/pages/Consumers'; import './index.less'; import TopicDetailHealthCheck from '@src/components/CardBar/TopicDetailHealthCheck'; import { hashDataParse } from '@src/constants/common'; @@ -115,35 +118,34 @@ const TopicDetail = (props: any) => { useEffect(() => { global?.clusterInfo?.id && hashDataParse(location.hash).topicName ? Utils.request(Api.getTopicMetadata(+global?.clusterInfo?.id, hashDataParse(location.hash)?.topicName), { - init: { - errorNoTips: true, - }, - }) - .then((topicData: any) => { - if (topicData?.exist && !hashDataParse(location.hash).groupName) { - setHashData(topicData); - setVisible(true); - } else { + init: { + errorNoTips: true, + }, + }) + .then((topicData: any) => { + if (topicData?.exist && !hashDataParse(location.hash).groupName) { + setHashData(topicData); + setVisible(true); + } else { + history.replace(`/cluster/${urlParams?.clusterId}/topic/list`); + // history.push(`/`); + setVisible(false); + } + }) + .catch((err) => { history.replace(`/cluster/${urlParams?.clusterId}/topic/list`); - // history.push(`/`); setVisible(false); - } - }) - .catch((err) => { - history.replace(`/cluster/${urlParams?.clusterId}/topic/list`); - setVisible(false); - notification.error({ - message: '错误', - duration: 3, - description: `${'Topic不存在或Topic名称有误'}`, - }); - }) + notification.error({ + message: '错误', + description: 'Topic不存在或Topic名称有误', + }); + }) : setVisible(false); }, [hashDataParse(location.hash).topicName, global?.clusterInfo]); return ( {hashData?.topicName} @@ -192,12 +194,9 @@ const TopicDetail = (props: any) => { {positionType === 'Messages' && } - + {positionType === 'ConsumerGroups' && ( + + )} {positionType === 'ACLs' && } diff --git a/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts b/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts index 13422a46..50e6ac71 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts +++ b/km-console/packages/layout-clusters-fe/src/pages/pageRoutes.ts @@ -10,7 +10,7 @@ import BrokerControllerChangeLog from './BrokerControllerChangeLog'; import TopicBoard from './TopicDashboard'; import TopicList from './TopicList'; -import Consumers from './Consumers/index'; +import Consumers from './ConsumerGroup'; import Jobs from './Jobs';