@@ -258,7 +266,7 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
}}
>
{metricChartData.map((data) => {
- const { metricName, metricUnit, metricLines } = data;
+ const { metricName, metricUnit, metricLines, showLegend } = data;
return (
@@ -301,7 +309,7 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
eventBus={busInstance}
propChartData={metricLines}
optionMergeProps={{ replaceMerge: curHeaderOptions.isAutoReload ? ['xAxis'] : ['series'] }}
- {...getChartConfig(`${metricName}{unit|(${metricUnit})}`, metricLines.length)}
+ {...getChartConfig(`${metricName}{unit|(${metricUnit})}`, metricLines.length, showLegend)}
/>
);
@@ -321,4 +329,4 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
);
};
-export default DashboardDragChart;
+export default DraggableCharts;
diff --git a/km-console/packages/layout-clusters-fe/src/components/Message/index.tsx b/km-console/packages/layout-clusters-fe/src/components/Message/index.tsx
new file mode 100644
index 00000000..5791939c
--- /dev/null
+++ b/km-console/packages/layout-clusters-fe/src/components/Message/index.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { IconFont } from '@knowdesign/icons';
+import { message } from 'knowdesign';
+import { ArgsProps, ConfigOnClose } from 'knowdesign/es/basic/message';
+
+type ConfigContent = React.ReactNode;
+type ConfigDuration = number | (() => void);
+type JointContent = ConfigContent | ArgsProps;
+
+message.config({
+ top: 16,
+});
+
+function isArgsProps(content: JointContent): content is ArgsProps {
+ return Object.prototype.toString.call(content) === '[object Object]' && !!(content as ArgsProps).content;
+}
+
+const openMessage = (
+ type: 'info' | 'success' | 'warning' | 'error',
+ content: JointContent,
+ duration?: ConfigDuration,
+ onClose?: ConfigOnClose
+) => {
+ if (isArgsProps(content)) {
+ message[type]({
+ icon:
,
+ ...content,
+ });
+ } else {
+ message[type]({
+ icon:
,
+ content,
+ duration,
+ onClose,
+ });
+ }
+};
+
+const customMessage = {
+ info(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose) {
+ openMessage('info', content, duration, onClose);
+ },
+ success(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose) {
+ openMessage('success', content, duration, onClose);
+ },
+ warning(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose) {
+ openMessage('warning', content, duration, onClose);
+ },
+ error(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose) {
+ openMessage('error', content, duration, onClose);
+ },
+};
+
+export default customMessage;
diff --git a/km-console/packages/layout-clusters-fe/src/components/Notification/index.tsx b/km-console/packages/layout-clusters-fe/src/components/Notification/index.tsx
new file mode 100644
index 00000000..9673d511
--- /dev/null
+++ b/km-console/packages/layout-clusters-fe/src/components/Notification/index.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { notification } from 'knowdesign';
+import { ArgsProps } from 'knowdesign/es/basic/notification';
+import { IconFont } from '@knowdesign/icons';
+
+notification.config({
+ top: 16,
+ duration: 3,
+});
+
+const open = (type: 'info' | 'success' | 'warning' | 'error', content: ArgsProps) => {
+ notification[type]({
+ icon:
,
+ ...content,
+ });
+};
+
+const customNotification = {
+ info(content: ArgsProps) {
+ open('info', content);
+ },
+ success(content: ArgsProps) {
+ open('success', content);
+ },
+ warning(content: ArgsProps) {
+ open('warning', content);
+ },
+ error(content: ArgsProps) {
+ open('error', content);
+ },
+};
+
+export default customNotification;
diff --git a/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaChange.tsx b/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaChange.tsx
index 16637b9c..cd4f05ba 100644
--- a/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaChange.tsx
+++ b/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaChange.tsx
@@ -15,10 +15,10 @@ import {
Tag,
Utils,
AppContainer,
- message,
Divider,
Space,
} from 'knowdesign';
+import message from '@src/components/Message';
import './index.less';
import Api, { MetricType } from '@src/api/index';
import moment from 'moment';
diff --git a/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaMove.tsx b/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaMove.tsx
index a6089e80..a93b26ef 100644
--- a/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaMove.tsx
+++ b/km-console/packages/layout-clusters-fe/src/components/TopicJob/ReplicaMove.tsx
@@ -14,12 +14,12 @@ import {
Table,
Utils,
AppContainer,
- message,
Space,
Divider,
Transfer,
Tooltip,
} from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import './index.less';
import Api, { MetricType } from '@src/api/index';
diff --git a/km-console/packages/layout-clusters-fe/src/constants/axiosConfig.ts b/km-console/packages/layout-clusters-fe/src/constants/axiosConfig.ts
index fb0ddf44..5a087ca0 100644
--- a/km-console/packages/layout-clusters-fe/src/constants/axiosConfig.ts
+++ b/km-console/packages/layout-clusters-fe/src/constants/axiosConfig.ts
@@ -1,4 +1,6 @@
-import { notification, Utils } from 'knowdesign';
+import { Utils } from 'knowdesign';
+import notification from '@src/components/Notification';
+
const { EventBus } = Utils;
export const licenseEventBus = new EventBus();
@@ -42,8 +44,8 @@ serviceInstance.interceptors.response.use(
licenseEventBus.emit('licenseError', desc);
} else {
notification.error({
- message: desc,
- duration: 3,
+ message: '错误信息',
+ description: desc,
});
}
throw res;
@@ -83,36 +85,31 @@ const dealResponse = (error: any) => {
case 405:
notification.error({
message: '错误',
- duration: 3,
description: `${error.response.data.message || '请求方式错误'}`,
});
break;
case 500:
notification.error({
message: '错误',
- duration: 3,
description: '服务错误,请重试!',
});
break;
case 502:
notification.error({
message: '错误',
- duration: 3,
description: '网络错误,请重试!',
});
break;
default:
notification.error({
message: '连接出错',
- duration: 3,
description: `${error.response.status}`,
});
}
} else {
notification.error({
+ message: '连接超时!',
description: '请重试或检查服务',
- message: '连接超时! ',
- duration: 3,
});
}
return Promise.reject(error);
diff --git a/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts b/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts
index 955bcfa0..f566e850 100644
--- a/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts
+++ b/km-console/packages/layout-clusters-fe/src/constants/chartConfig.ts
@@ -12,30 +12,38 @@ export interface MetricInfo {
}
// 接口返回图表原始数据类型
-export interface MetricDefaultChartDataType {
+export interface OriginMetricData {
metricName: string;
- metricLines: {
+ metricLines?: {
name: string;
createTime: number;
updateTime: number;
metricPoints: {
aggType: string;
timeStamp: number;
- value: number;
+ value: string;
createTime: number;
updateTime: number;
}[];
}[];
+ metricPoints?: {
+ aggType: string;
+ name: string;
+ timeStamp: number;
+ value: string;
+ }[];
}
// 格式化后图表数据类型
-export interface MetricChartDataType {
+export interface FormattedMetricData {
metricName: string;
metricUnit: string;
metricLines: {
name: string;
data: (string | number)[][];
}[];
+ showLegend: boolean;
+ targetUnit: [string, number] | undefined;
}
// 图表颜色库
@@ -55,22 +63,38 @@ export const CHART_COLOR_LIST = [
'#C9E795',
];
-// 图表存储单位换算
-export const UNIT_MAP = {
+// 图表存储单位
+export const MEMORY_MAP = {
TB: Math.pow(1024, 4),
GB: Math.pow(1024, 3),
MB: Math.pow(1024, 2),
KB: 1024,
};
-export const getUnit = (value: number) => Object.entries(UNIT_MAP).find(([, size]) => value / size >= 1) || ['Byte', 1];
-
-// 图表数字单位换算
-export const DATA_NUMBER_MAP = {
+// 图表时间单位
+export const TIME_MAP = {
+ h: 1000 * 60 * 60,
+ min: 1000 * 60,
+ s: 1000,
+};
+// 图表数字单位
+export const NUM_MAP = {
十亿: Math.pow(1000, 3),
百万: Math.pow(1000, 2),
千: 1000,
};
-export const getDataNumberUnit = (value: number) => Object.entries(DATA_NUMBER_MAP).find(([, size]) => value / size >= 1) || ['', 1];
+const calculateUnit = (map: { [unit: string]: number }, targetValue: number) => {
+ return Object.entries(map).find(([, size]) => targetValue / size >= 1);
+};
+const getMemoryUnit = (value: number) => calculateUnit(MEMORY_MAP, value) || ['Byte', 1];
+const getTimeUnit = (value: number) => calculateUnit(TIME_MAP, value) || ['ms', 1];
+const getNumUnit = (value: number) => calculateUnit(NUM_MAP, value) || ['', 1];
+
+export const getDataUnit = {
+ Memory: getMemoryUnit,
+ Time: getTimeUnit,
+ Num: getNumUnit,
+};
+export type DataUnitType = keyof typeof getDataUnit;
// 图表补点间隔计算
export const SUPPLEMENTARY_INTERVAL_MAP = {
@@ -112,9 +136,9 @@ export const resolveMetricsRank = (metricList: MetricInfo[]) => {
};
};
-// 补点
+// 图表补点
export const supplementaryPoints = (
- lines: MetricChartDataType['metricLines'],
+ lines: FormattedMetricData['metricLines'],
timeRange: readonly [number, number],
extraCallback?: (point: [number, 0]) => any[]
): void => {
@@ -165,19 +189,39 @@ export const supplementaryPoints = (
// 格式化图表数据
export const formatChartData = (
// 图表源数据
- metricData: MetricDefaultChartDataType[],
+ metricsData: OriginMetricData[],
// 获取指标单位
getMetricDefine: (type: MetricType, metric: string) => MetricsDefine[keyof MetricsDefine],
// 指标类型
metricType: MetricType,
// 图表时间范围,用于补点
timeRange: readonly [number, number],
- transformUnit: [string, number] = undefined
-): MetricChartDataType[] => {
- return metricData.map(({ metricName, metricLines }) => {
+ targetUnit: [string, number] = undefined
+): FormattedMetricData[] => {
+ return metricsData.map((originData) => {
+ const { metricName } = originData;
const curMetricInfo = (getMetricDefine && getMetricDefine(metricType, metricName)) || null;
- const isByteUnit = curMetricInfo?.unit?.toLowerCase().includes('byte');
+ let showLegend = true;
+ let metricLines = [];
let maxValue = -1;
+ let unitType: DataUnitType;
+
+ if (originData?.metricLines && originData?.metricLines !== null) {
+ metricLines = originData.metricLines;
+ } else {
+ showLegend = false;
+ metricLines = [
+ {
+ name: metricName,
+ metricPoints: originData.metricPoints,
+ },
+ ];
+ }
+
+ {
+ const originUnit = curMetricInfo?.unit?.toLowerCase();
+ unitType = originUnit.includes('byte') ? 'Memory' : originUnit.includes('ms') ? 'Time' : 'Num';
+ }
const PointsMapMethod = ({ timeStamp, value }: { timeStamp: number; value: string | number }) => {
let parsedValue: string | number = Number(value);
@@ -194,7 +238,7 @@ export const formatChartData = (
};
// 初始化返回结构
- const chartData = {
+ const chartData: FormattedMetricData = {
metricName,
metricUnit: curMetricInfo?.unit || '',
metricLines: metricLines
@@ -203,17 +247,21 @@ export const formatChartData = (
name,
data: metricPoints.map(PointsMapMethod),
})),
+ showLegend,
+ targetUnit: undefined,
};
// 按时间先后进行对图表点排序
chartData.metricLines.forEach(({ data }) => data.sort((a, b) => (a[0] as number) - (b[0] as number)));
// 图表值单位转换
if (maxValue > 0) {
- const [unitName, unitSize]: [string, number] = transformUnit || isByteUnit ? getUnit(maxValue) : getDataNumberUnit(maxValue);
- chartData.metricUnit = isByteUnit
- ? chartData.metricUnit.toLowerCase().replace('byte', unitName)
- : `${unitName}${chartData.metricUnit}`;
- chartData.metricLines.forEach(({ data }) => data.forEach((point: any) => (point[1] /= unitSize)));
+ const [unitName, unitSize]: [string, number] = targetUnit || getDataUnit[unitType](maxValue);
+ chartData.targetUnit = [unitName, unitSize];
+ chartData.metricUnit =
+ unitType !== 'Num'
+ ? chartData.metricUnit.toLowerCase().replace(unitType === 'Memory' ? 'byte' : 'ms', unitName)
+ : `${unitName}${chartData.metricUnit}`;
+ chartData.metricLines.forEach(({ data }) => data.forEach((point: any) => parseFloat((point[1] /= unitSize).toFixed(3))));
}
// 补点
@@ -231,7 +279,7 @@ const tooltipFormatter = (date: any, arr: any, tooltip: any) => {
-
+
${item.seriesName}
@@ -324,6 +372,7 @@ export const getBasicChartConfig = (props: any = {}) => {
tooltip: false,
...legend,
},
+ color: CHART_COLOR_LIST,
// 横坐标配置
xAxis: {
type: 'category',
diff --git a/km-console/packages/layout-clusters-fe/src/pages/BrokerDashboard/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/BrokerDashboard/index.tsx
index a3398dec..a026d43f 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/BrokerDashboard/index.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/BrokerDashboard/index.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { MetricType } from '@src/api';
import BrokerHealthCheck from '@src/components/CardBar/BrokerHealthCheck';
-import DashboardDragChart from '@src/components/DashboardDragChart';
+import DraggableCharts from '@src/components/DraggableCharts';
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
import { AppContainer } from 'knowdesign';
@@ -19,7 +19,7 @@ const BrokerDashboard = (): JSX.Element => {
/>
-
+
>
);
};
diff --git a/km-console/packages/layout-clusters-fe/src/pages/BrokerDetail/ConfigurationEdit.tsx b/km-console/packages/layout-clusters-fe/src/pages/BrokerDetail/ConfigurationEdit.tsx
index a362acc0..f4fda39a 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/BrokerDetail/ConfigurationEdit.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/BrokerDetail/ConfigurationEdit.tsx
@@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
-import { Drawer, Form, Input, Space, Button, Checkbox, Utils, Row, Col, Divider, message } from 'knowdesign';
+import { Drawer, Form, Input, Space, Button, Checkbox, Utils, Row, Col, Divider } from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import { useParams } from 'react-router-dom';
import Api from '@src/api';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/BrokerList/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/BrokerList/config.tsx
index a0f6cbe5..d2b28cda 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/BrokerList/config.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/BrokerList/config.tsx
@@ -3,6 +3,7 @@ import React from 'react';
import { timeFormat, getSizeAndUnit } from '../../constants/common';
import moment from 'moment';
import { Tag, Tooltip } from 'knowdesign';
+import { IconFont } from '@knowdesign/icons';
export const getBrokerListColumns = (arg?: any) => {
const columns = [
@@ -82,6 +83,14 @@ export const getBrokerListColumns = (arg?: any) => {
dataIndex: 'jmxPort',
key: 'jmxPort',
width: 100,
+ render: (t: string, r: any) => {
+ return (
+
+
+ {t}
+
+ );
+ },
},
{
title: 'Partitions',
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/Consumers/ConsumerGroupDetail.tsx b/km-console/packages/layout-clusters-fe/src/pages/Consumers/ConsumerGroupDetail.tsx
index 1e057d29..8c220330 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/Consumers/ConsumerGroupDetail.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/Consumers/ConsumerGroupDetail.tsx
@@ -98,7 +98,7 @@ export default (props: any) => {
dataIndex: 'OffsetConsumed',
key: 'OffsetConsumed',
render: (v: any, record: any) => {
- return record?.latestMetrics?.metrics?.OffsetConsumed;
+ return record?.latestMetrics?.metrics?.OffsetConsumed.toLocaleString();
},
sorter: true,
// sorter: {
@@ -115,7 +115,7 @@ export default (props: any) => {
dataIndex: 'LogEndOffset',
key: 'LogEndOffset',
render: (v: any, record: any) => {
- return record?.latestMetrics?.metrics?.LogEndOffset;
+ return record?.latestMetrics?.metrics?.LogEndOffset.toLocaleString();
},
sorter: true,
// sorter: {
@@ -132,7 +132,7 @@ export default (props: any) => {
dataIndex: 'Lag',
key: 'Lag',
render: (v: any, record: any) => {
- return record?.latestMetrics?.metrics?.Lag;
+ return record?.latestMetrics?.metrics?.Lag.toLocaleString();
},
sorter: true,
// sorter: {
diff --git a/km-console/packages/layout-clusters-fe/src/pages/Consumers/ResetOffsetDrawer.tsx b/km-console/packages/layout-clusters-fe/src/pages/Consumers/ResetOffsetDrawer.tsx
index c079393b..bba5059e 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/Consumers/ResetOffsetDrawer.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/Consumers/ResetOffsetDrawer.tsx
@@ -1,11 +1,14 @@
import React, { useState, useEffect } from 'react';
-import { Button, DatePicker, Drawer, Form, notification, Radio, Utils, Space, Divider, message } from 'knowdesign';
+import { Button, DatePicker, Drawer, Form, Radio, Utils, Space, Divider } from 'knowdesign';
+import notification from '@src/components/Notification';
+
+import message from '@src/components/Message';
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 CustomSelectResetTime = (props: { value?: string; onChange?: (val: number | string) => void }) => {
const { value, onChange } = props;
const [timeSetMode, setTimeSetMode] = useState('newest');
useEffect(() => {
@@ -81,7 +84,7 @@ export default (props: any) => {
tableData = customFormRef.current.getTableData();
}
const formData = form.getFieldsValue();
- let resetParams: any = {
+ const resetParams: any = {
clusterId: clusterPhyId,
createIfNotExist: false,
groupName: record.groupName,
diff --git a/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx
index 4e06d92a..53c0f02c 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/Consumers/index.tsx
@@ -108,11 +108,13 @@ const AutoPage = (props: any) => {
title: 'Max Lag',
dataIndex: 'maxLag',
key: 'maxLag',
+ render: (t: number) => (t ? t.toLocaleString() : '-'),
},
{
title: 'Member数',
dataIndex: 'memberCount',
key: 'memberCount',
+ render: (t: number) => (t ? t.toLocaleString() : '-'),
},
];
diff --git a/km-console/packages/layout-clusters-fe/src/pages/Jobs/ExpandedRow.tsx b/km-console/packages/layout-clusters-fe/src/pages/Jobs/ExpandedRow.tsx
index 399be7a7..4de68b47 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/Jobs/ExpandedRow.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/Jobs/ExpandedRow.tsx
@@ -153,6 +153,7 @@ export const ExpandedRow: any = ({ record, data, loading }: any) => {
Github:
- 5K
+ 5.4K
+ Star的项目 Know Streaming
diff --git a/km-console/packages/layout-clusters-fe/src/pages/Login/login.tsx b/km-console/packages/layout-clusters-fe/src/pages/Login/login.tsx
index 39c72a76..0400dbf0 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/Login/login.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/Login/login.tsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { Form, Button, Input, Row, InputNumber, Utils, message } from 'knowdesign';
+import { Form, Button, Input, Row, InputNumber, Utils } from 'knowdesign';
+import message from '@src/components/Message';
import { FormMap } from './config';
import Api from '../../api';
import { useHistory } from 'react-router-dom';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx
index 7435d9a7..e5c1f649 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/AccessCluster.tsx
@@ -1,4 +1,5 @@
-import { Button, Divider, Drawer, Form, Input, InputNumber, message, Radio, Select, Spin, Space, Utils } from 'knowdesign';
+import { Button, Divider, Drawer, Form, Input, InputNumber, Radio, Select, Spin, Space, Utils } from 'knowdesign';
+import message from '@src/components/Message';
import * as React from 'react';
import { useIntl } from 'react-intl';
import api from '@src/api';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/List.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/List.tsx
index d15cffcb..18723cd8 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/List.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/List.tsx
@@ -1,4 +1,5 @@
-import { AppContainer, Divider, Form, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign';
+import { AppContainer, Divider, Form, Input, List, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import moment from 'moment';
import API from '@src/api';
@@ -11,7 +12,7 @@ import { useIntl } from 'react-intl';
import api, { MetricType } from '@src/api';
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
import { ClustersPermissionMap } from '../CommonConfig';
-import { getUnit, getDataNumberUnit } from '@src/constants/chartConfig';
+import { getDataUnit } from '@src/constants/chartConfig';
import SmallChart from '@src/components/SmallChart';
import { SearchParams } from './HomePage';
@@ -235,14 +236,14 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
// 如果单位是 字节 ,进行单位换算
if (line.unit.toLowerCase().includes('byte')) {
- const [unit, size] = getUnit(line.value);
+ const [unit, size] = getDataUnit['Memory'](line.value);
line.value = Number((line.value / size).toFixed(2));
line.unit = line.unit.toLowerCase().replace('byte', unit);
}
// Messages 指标值特殊处理
if (line.metricName === 'LeaderMessages') {
- const [unit, size] = getDataNumberUnit(line.value);
+ const [unit, size] = getDataUnit['Num'](line.value);
line.value = Number((line.value / size).toFixed(2));
line.unit = unit + line.unit;
}
diff --git a/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/EditDrawer.tsx b/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/EditDrawer.tsx
index 9b2a7203..8afcdccc 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/EditDrawer.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/EditDrawer.tsx
@@ -1,5 +1,6 @@
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
-import { Button, Form, Input, Select, message, Drawer, Space, Divider, Utils, Radio, AutoComplete, Alert } from 'knowdesign';
+import { Button, Form, Input, Select, Drawer, Space, Divider, Utils, Radio, AutoComplete, Alert } from 'knowdesign';
+import message from '@src/components/Message';
import api from '@src/api';
import { useParams } from 'react-router-dom';
import { UsersProps } from '../SecurityUsers';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx
index a98f6269..d361f3e7 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/SecurityACLs/index.tsx
@@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
-import { Button, Form, Input, Select, Modal, message, ProTable, AppContainer, DKSBreadcrumb, Utils, Divider } from 'knowdesign';
+import { Button, Form, Input, Select, Modal, ProTable, AppContainer, DKSBreadcrumb, Utils, Divider } from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import ACLsCardBar from '@src/components/CardBar/ACLsCardBar';
import api from '@src/api';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/SecurityUsers/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/SecurityUsers/index.tsx
index 9f493bd7..6291ed42 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/SecurityUsers/index.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/SecurityUsers/index.tsx
@@ -4,7 +4,6 @@ import {
Form,
Input,
Modal,
- message,
ProTable,
Drawer,
Space,
@@ -16,6 +15,7 @@ import {
Tooltip,
Alert,
} from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import { CloseOutlined, EyeInvisibleOutlined, EyeOutlined, LoadingOutlined } from '@ant-design/icons';
import './index.less';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/config.tsx b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/config.tsx
index 65ec2fd9..405f95c0 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/config.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/config.tsx
@@ -14,7 +14,7 @@ const messagesInTooltipFormatter = (date: any, arr: any) => {
-
+
${params.seriesName}
@@ -55,7 +55,7 @@ const messagesInTooltipFormatter = (date: any, arr: any) => {
};
export const getChartConfig = (props: any) => {
- const { metricName, lineColor, isDefaultMetric = false } = props;
+ const { lineColor, isDefaultMetric = false } = props;
return {
option: getBasicChartConfig({
// TODO: time 轴图表联动有问题,先切换为 category
@@ -63,7 +63,6 @@ export const getChartConfig = (props: any) => {
title: { show: false },
legend: { show: false },
grid: { top: 24, bottom: 12 },
- lineColor: [lineColor],
tooltip: isDefaultMetric
? {
formatter: function (params: any) {
@@ -87,6 +86,7 @@ export const getChartConfig = (props: any) => {
smooth: 0.25,
symbol: 'emptyCircle',
symbolSize: 4,
+ color: '#556ee6',
// 面积图样式
areaStyle: {
color: lineColor,
diff --git a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx
index 3df6ee1e..b9337f09 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/SingleClusterDetail/DetailChart/index.tsx
@@ -5,16 +5,16 @@ import { arrayMoveImmutable } from 'array-move';
import api from '@src/api';
import { useParams } from 'react-router-dom';
import {
- MetricDefaultChartDataType,
- MetricChartDataType,
+ OriginMetricData,
+ FormattedMetricData,
formatChartData,
supplementaryPoints,
resolveMetricsRank,
MetricInfo,
} from '@src/constants/chartConfig';
import { MetricType } from '@src/api';
-import { getDataNumberUnit, getUnit } from '@src/constants/chartConfig';
-import SingleChartHeader, { KsHeaderOptions } from '@src/components/SingleChartHeader';
+import { getDataUnit } from '@src/constants/chartConfig';
+import ChartOperateBar, { KsHeaderOptions } from '@src/components/ChartOperateBar';
import RenderEmpty from '@src/components/RenderEmpty';
import DragGroup from '@src/components/DragGroup';
import { getChartConfig } from './config';
@@ -162,13 +162,13 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
metricsNames: selectedMetricNames.filter((name) => name !== DEFAULT_METRIC),
},
}).then(
- (res: MetricDefaultChartDataType[]) => {
+ (res: OriginMetricData[]) => {
// 如果当前请求不是最新请求,则不做任何操作
if (curFetchingTimestamp.current.messagesIn !== curTimestamp) {
return;
}
- const formattedMetricData: MetricChartDataType[] = formatChartData(
+ const formattedMetricData: FormattedMetricData[] = formatChartData(
res,
global.getMetricDefine || {},
MetricType.Cluster,
@@ -224,7 +224,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
let valueWithUnit = Number(value);
let unit = ((global.getMetricDefine && global.getMetricDefine(MetricType.Cluster, key)?.unit) || '') as string;
if (unit.toLowerCase().includes('byte')) {
- const [unitName, unitSize]: [string, number] = getUnit(Number(value));
+ const [unitName, unitSize]: [string, number] = getDataUnit['Memory'](Number(value));
unit = unit.toLowerCase().replace('byte', unitName);
valueWithUnit /= unitSize;
}
@@ -235,7 +235,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
};
return returnValue;
});
- return [timeStamp, values.MessagesIn || '0', valuesWithUnit] as [number, number | string, typeof valuesWithUnit];
+ return [timeStamp, parsedValue || '0', valuesWithUnit] as [number, number | string, typeof valuesWithUnit];
});
result.sort((a, b) => (a[0] as number) - (b[0] as number));
const line = {
@@ -244,9 +244,9 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
data: result as any,
};
if (maxValue > 0) {
- const [unitName, unitSize]: [string, number] = getDataNumberUnit(maxValue);
+ const [unitName, unitSize]: [string, number] = getDataUnit['Num'](maxValue);
line.unit = `${unitName}${line.unit}`;
- result.forEach((point) => ((point[1] as number) /= unitSize));
+ result.forEach((point) => parseFloat(((point[1] as number) /= unitSize).toFixed(3)));
}
if (result.length) {
@@ -308,11 +308,11 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
return (
- {
item.configItem.indexOf('Group Re-Balance') > -1
? 'ReBalance'
: item.configItem.includes('副本未同步')
- ? 'UNDER_REPLICA'
- : item.configItem;
+ ? 'UNDER_REPLICA'
+ : item.configItem;
values[`weight_${item.configItemName}`] = itemValue?.weight;
values[`value_${item.configItemName}`] = itemValue?.value;
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TestingConsumer/Consume.tsx b/km-console/packages/layout-clusters-fe/src/pages/TestingConsumer/Consume.tsx
index be43d454..90625de8 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TestingConsumer/Consume.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TestingConsumer/Consume.tsx
@@ -1,6 +1,7 @@
/* eslint-disable no-case-declarations */
import { DownloadOutlined } from '@ant-design/icons';
-import { AppContainer, Divider, message, Tooltip, Utils } from 'knowdesign';
+import { AppContainer, Divider, Tooltip, Utils } from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import * as React from 'react';
import moment from 'moment';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/Produce.tsx b/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/Produce.tsx
index 8f5e3479..18f0b2c5 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/Produce.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/Produce.tsx
@@ -1,4 +1,5 @@
-import { AppContainer, Form, message, Tabs, Utils } from 'knowdesign';
+import { AppContainer, Form, Tabs, Utils } from 'knowdesign';
+import message from '@src/components/Message';
import * as React from 'react';
import ConfigForm from './component/ConfigFrom';
import TestResult from '../TestingConsumer/component/Result';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/component/EditTable.tsx b/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/component/EditTable.tsx
index a5300af2..55b06e25 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/component/EditTable.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TestingProduce/component/EditTable.tsx
@@ -1,6 +1,7 @@
/* eslint-disable react/display-name */
import React, { useState } from 'react';
-import { Table, Input, InputNumber, Popconfirm, Form, Typography, Button, message, Select } from 'knowdesign';
+import { Table, Input, InputNumber, Popconfirm, Form, Typography, Button, Select } from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import './style/edit-table.less';
import { CheckOutlined, CloseOutlined, PlusSquareOutlined } from '@ant-design/icons';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDashboard/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDashboard/index.tsx
index 72797227..ccf4104f 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TopicDashboard/index.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDashboard/index.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { MetricType } from '@src/api';
import TopicHealthCheck from '@src/components/CardBar/TopicHealthCheck';
-import DashboardDragChart from '@src/components/DashboardDragChart';
+import DraggableCharts from '@src/components/DraggableCharts';
import { AppContainer } from 'knowdesign';
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
@@ -19,7 +19,7 @@ const TopicDashboard = () => {
/>
-
+
>
);
};
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConfigurationEdit.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConfigurationEdit.tsx
index 7fc4ab80..ff8c6cd0 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConfigurationEdit.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TopicDetail/ConfigurationEdit.tsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { Drawer, Form, Input, Space, Button, Utils, Row, Col, Divider, message } from 'knowdesign';
+import { Drawer, Form, Input, Space, Button, Utils, Row, Col, Divider } from 'knowdesign';
+import message from '@src/components/Message';
import { IconFont } from '@knowdesign/icons';
import { useParams } from 'react-router-dom';
import Api from '@src/api';
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/TopicList/Create.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/Create.tsx
index 288b99c7..6f1cb667 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/Create.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/Create.tsx
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
-import { Alert, Button, Checkbox, Divider, Drawer, Form, Input, InputNumber, Modal, notification, Select, Utils } from 'knowdesign';
+import { Alert, Button, Checkbox, Divider, Drawer, Form, Input, InputNumber, Modal, Select, Utils } from 'knowdesign';
+import notification from '@src/components/Notification';
import { PlusOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import Api from '@src/api/index';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/Delete.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/Delete.tsx
index 75af2070..69109225 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/Delete.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/Delete.tsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
-import { Button, Form, Input, Modal, notification, Utils } from 'knowdesign';
+import { Button, Form, Input, Modal, Utils } from 'knowdesign';
+import notification from '@src/components/Notification';
import { IconFont } from '@knowdesign/icons';
import Api from '@src/api/index';
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/ExpandPartition.tsx b/km-console/packages/layout-clusters-fe/src/pages/TopicList/ExpandPartition.tsx
index b17a41ab..bf87fb59 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/ExpandPartition.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/ExpandPartition.tsx
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
-import { AppContainer, Button, Divider, Drawer, Form, InputNumber, notification, SingleChart, Space, Spin, Utils } from 'knowdesign';
+import { AppContainer, Button, Divider, Drawer, Form, InputNumber, SingleChart, Space, Spin, Tooltip, Utils } from 'knowdesign';
+import notification from '@src/components/Notification';
import Api, { MetricType } from '@src/api/index';
-import { getBasicChartConfig, getUnit } from '@src/constants/chartConfig';
-import { formatChartData, MetricDefaultChartDataType } from '@src/constants/chartConfig';
+import { getBasicChartConfig, getDataUnit } from '@src/constants/chartConfig';
+import { formatChartData, OriginMetricData } from '@src/constants/chartConfig';
const ExpandPartition = (props: { record: any; onConfirm: () => void }) => {
const [global] = AppContainer.useGlobalValue();
@@ -60,7 +61,7 @@ const ExpandPartition = (props: { record: any; onConfirm: () => void }) => {
);
const empiricalMinValue = 10 * 1024 * record.partitionNum;
- const lines = data.map((metric: MetricDefaultChartDataType) => {
+ const lines = data.map((metric: OriginMetricData) => {
const child = metric.metricLines[0];
child.name = metric.metricName;
return child;
@@ -87,7 +88,7 @@ const ExpandPartition = (props: { record: any; onConfirm: () => void }) => {
});
}, [expandPartitionsVisible]);
const formattedMinBytesInOut = (v: number) => {
- const [unit, size] = getUnit(v);
+ const [unit, size] = getDataUnit['Memory'](v);
return `${(v / size).toFixed(2)}${unit}/s`;
};
return (
@@ -130,11 +131,15 @@ const ExpandPartition = (props: { record: any; onConfirm: () => void }) => {
Topic名称 :
- {record.topicName}
+
+ {record.topicName}
+
描述 :
- {record.description}
+
+ {record.description || '-'}
+
diff --git a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less
index 453363c6..0fa44077 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less
+++ b/km-console/packages/layout-clusters-fe/src/pages/TopicList/index.less
@@ -142,6 +142,7 @@
color: #74788d;
}
.desc-field {
+ flex-shrink: 0;
width: 34px;
}
.val,
@@ -149,17 +150,28 @@
color: #495057;
letter-spacing: 0;
font-weight: 400;
- }
- .val {
- width: 105px;
margin-left: 12px;
}
- .desc-val {
- width: 809px;
- height: 36px;
+ .val {
+ max-width: 208px;
overflow: hidden;
- -webkit-box-orient: vertical;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .desc-val {
+ height: 36px;
+ display: -webkit-box;
+ overflow: hidden;
+ text-overflow: ellipsis;
-webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ word-break: break-all;
+ }
+ &:first-child {
+ margin-right: 40px;
+ }
+ &:last-child {
+ flex: 1;
}
}
}
diff --git a/km-console/packages/layout-clusters-fe/src/pages/index.tsx b/km-console/packages/layout-clusters-fe/src/pages/index.tsx
index 4e1d1808..16d73de9 100644
--- a/km-console/packages/layout-clusters-fe/src/pages/index.tsx
+++ b/km-console/packages/layout-clusters-fe/src/pages/index.tsx
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Redirect, useHistory, useLocation } from 'react-router-dom';
-import { DProLayout, AppContainer, RouteGuard, notification, Spin } from 'knowdesign';
+import { DProLayout, AppContainer, RouteGuard, Spin } from 'knowdesign';
+import notification from '@src/components/Notification';
import { pageRoutes } from './pageRoutes';
import { leftMenus, systemKey } from '@src/constants/menu';
import { ClustersPermissionMap } from './CommonConfig';
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';
diff --git a/km-console/packages/layout-clusters-fe/src/style-addition.less b/km-console/packages/layout-clusters-fe/src/style-addition.less
index 451c31bf..f4b7e04b 100644
--- a/km-console/packages/layout-clusters-fe/src/style-addition.less
+++ b/km-console/packages/layout-clusters-fe/src/style-addition.less
@@ -11,6 +11,7 @@
@select-item-selected-font-weight: normal;
@btn-danger-bg: #F5483B;
@btn-danger-border: #F5483B;
+@notification-icon-size: 20px;
// 自定义变量
// Input
@input-bg: rgba(33, 37, 41, 0.06);
@@ -656,3 +657,109 @@
.@{ant-prefix}-empty-img-default{
width: 100% !important;
}
+
+// message 样式覆盖
+.@{message-prefix-cls} {
+ &-notice-content {
+ padding: 8px 12px;
+ font-size: 14px;
+ border-radius: 4px;
+ background: #fff;
+ }
+ &-success,
+ &-info,
+ &-warning,
+ &-error,
+ &-loading {
+ margin: unset;
+ padding: unset;
+ color: #000;
+ border-style: unset;
+ border-width: unset;
+ border-radius: unset;
+ }
+
+ &-success {
+ background-color: unset;
+ border-color: unset;
+ }
+
+ &-info,
+ &-loading {
+ background-color: unset;
+ border-color: unset;
+ }
+
+ &-warning {
+ background-color: unset;
+ border-color: unset;
+ }
+
+ &-error {
+ background-color: unset;
+ border-color: unset;
+ }
+
+ .@{iconfont-css-prefix} {
+ top: 2px;
+ color: unset;
+ }
+}
+
+// Notification 样式覆盖
+.@{notification-prefix-cls} {
+ &-notice {
+ width: 348px;
+ color: #000;
+ background-color: #fff;
+ border: unset;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px 0 rgba(0,0,0,0.02), 0 4px 6px 6px rgba(0,0,0,0.02), 0 4px 6px 0 rgba(0,0,0,0.06);
+ &-message {
+ font-family: @font-family-bold;
+ font-size: 16px;
+ color: #000;
+ }
+ &-description {
+ font-size: 14px;
+ color: rgba(0,0,0,0.60);
+ }
+ &-with-icon &-message {
+ font-size: 16px;
+ margin-left: 26px;
+ }
+ &-with-icon &-description {
+ font-size: 14px;
+ margin-left: 26px;
+ }
+ &-icon {
+ top: 18px;
+ font-size: @notification-icon-size;
+ line-height: @notification-icon-size;
+ }
+ .@{iconfont-css-prefix}&-icon {
+ color: @notification-content-color !important;
+ }
+ &-success {
+ background-color: #fff;
+ border: unset;
+ }
+ &-warning {
+ background-color: #fff;
+ border: unset;
+ }
+ &-error {
+ background-color: #fff;
+ border: unset;
+ }
+ &-close {
+ color: rgba(0,0,0,0.60);
+
+ &:hover {
+ & when not (@theme = dark) {
+ color: shade(@white, 30%);
+ }
+ }
+ }
+ }
+}
diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java
index bc5b1c34..2fc0a4ff 100644
--- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java
+++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/CollectedMetricsLocalCache.java
@@ -24,11 +24,6 @@ public class CollectedMetricsLocalCache {
.maximumSize(10000)
.build();
- private static final Cache replicaMetricsValueCache = Caffeine.newBuilder()
- .expireAfterWrite(90, TimeUnit.SECONDS)
- .maximumSize(20000)
- .build();
-
public static Float getBrokerMetrics(String brokerMetricKey) {
return brokerMetricsCache.getIfPresent(brokerMetricKey);
}
@@ -64,17 +59,6 @@ public class CollectedMetricsLocalCache {
partitionMetricsCache.put(partitionMetricsKey, metricsList);
}
- public static Float getReplicaMetrics(String replicaMetricsKey) {
- return replicaMetricsValueCache.getIfPresent(replicaMetricsKey);
- }
-
- public static void putReplicaMetrics(String replicaMetricsKey, Float value) {
- if (value == null) {
- return;
- }
- replicaMetricsValueCache.put(replicaMetricsKey, value);
- }
-
public static String genBrokerMetricKey(Long clusterPhyId, Integer brokerId, String metricName) {
return clusterPhyId + "@" + brokerId + "@" + metricName;
}
diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/ZookeeperLocalCache.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/ZookeeperLocalCache.java
new file mode 100644
index 00000000..24f6c7f2
--- /dev/null
+++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/cache/ZookeeperLocalCache.java
@@ -0,0 +1,46 @@
+package com.xiaojukeji.know.streaming.km.core.cache;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper.fourletterword.BaseFourLetterWordCmdData;
+
+import java.util.concurrent.TimeUnit;
+
+public class ZookeeperLocalCache {
+ private static final Cache fourLetterCmdFailedServerCache = Caffeine.newBuilder()
+ .expireAfterWrite(10, TimeUnit.MINUTES)
+ .maximumSize(10000)
+ .build();
+
+ private static final Cache fourLetterCmdDataCache = Caffeine.newBuilder()
+ .expireAfterWrite(60, TimeUnit.SECONDS)
+ .maximumSize(10000)
+ .build();
+
+ public static boolean canUse(String host, int port, String cmd) {
+ String data = fourLetterCmdFailedServerCache.getIfPresent(gen4lwFailedKey(host, port, cmd));
+
+ return data == null;
+ }
+
+ public static void setFailed(String host, int port, String cmd) {
+ fourLetterCmdFailedServerCache.put(gen4lwFailedKey(host, port, cmd), "");
+ }
+
+ public static BaseFourLetterWordCmdData getData(String host, int port, String cmd) {
+ return fourLetterCmdDataCache.getIfPresent(gen4lwFailedKey(host, port, cmd));
+ }
+
+ public static void putData(String host, int port, String cmd, BaseFourLetterWordCmdData cmdData) {
+ fourLetterCmdDataCache.put(gen4lwFailedKey(host, port, cmd), cmdData);
+ }
+
+ /**************************************************** private method ****************************************************/
+
+ private static String gen4lwFailedKey(String host, int port, String cmd) {
+ return host + "@" + port + "@" + cmd;
+ }
+
+
+
+}
diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/BrokerService.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/BrokerService.java
index 62f03e65..c8c300a0 100644
--- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/BrokerService.java
+++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/BrokerService.java
@@ -67,4 +67,8 @@ public interface BrokerService {
* 获取总的Broker数
*/
Integer countAllBrokers();
+
+ boolean allServerDown(Long clusterPhyId);
+
+ boolean existServerDown(Long clusterPhyId);
}
diff --git a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java
index 93c343ff..e82882e1 100644
--- a/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java
+++ b/km-core/src/main/java/com/xiaojukeji/know/streaming/km/core/service/broker/impl/BrokerMetricServiceImpl.java
@@ -37,6 +37,8 @@ import com.xiaojukeji.know.streaming.km.core.service.version.metrics.BrokerMetri
import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ReplicaMetricVersionItems;
import com.xiaojukeji.know.streaming.km.persistence.es.dao.BrokerMetricESDAO;
import com.xiaojukeji.know.streaming.km.persistence.kafka.KafkaJMXClient;
+import org.apache.kafka.clients.admin.LogDirDescription;
+import org.apache.kafka.clients.admin.ReplicaInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -49,6 +51,7 @@ import java.util.*;
import java.util.stream.Collectors;
import static com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus.*;
+import static com.xiaojukeji.know.streaming.km.common.enums.version.VersionEnum.*;
/**
* @author didi
@@ -105,7 +108,11 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
registerVCHandler( BROKER_METHOD_GET_HEALTH_SCORE, this::getMetricHealthScore);
registerVCHandler( BROKER_METHOD_GET_PARTITIONS_SKEW, this::getPartitionsSkew);
registerVCHandler( BROKER_METHOD_GET_LEADERS_SKEW, this::getLeadersSkew);
- registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, this::getLogSize);
+// registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, this::getLogSize);
+
+ registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, V_0_10_0_0, V_1_0_0, "getLogSizeFromJmx", this::getLogSizeFromJmx);
+ registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, V_1_0_0, V_MAX, "getLogSizeFromClient", this::getLogSizeFromClient);
+
registerVCHandler( BROKER_METHOD_IS_BROKER_ALIVE, this::isBrokerAlive);
}
@@ -351,7 +358,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
);
}
- private Result getLogSize(VersionItemParam metricParam) {
+ private Result getLogSizeFromJmx(VersionItemParam metricParam) {
BrokerMetricParam param = (BrokerMetricParam)metricParam;
String metric = param.getMetric();
@@ -360,19 +367,17 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
List partitions = partitionService.listPartitionByBroker(clusterId, brokerId);
- JmxConnectorWrap jmxConnectorWrap = kafkaJMXClient.getClientWithCheck(clusterId, brokerId);
- if (ValidateUtils.isNull(jmxConnectorWrap)){return Result.buildFailure(VC_JMX_INIT_ERROR);}
-
Float logSizeSum = 0f;
for(Partition p : partitions) {
try {
- Result metricsResult = replicaMetricService.collectReplicaMetricsFromKafkaWithCache(
+ Result metricsResult = replicaMetricService.collectReplicaMetricsFromKafka(
clusterId,
p.getTopicName(),
brokerId,
p.getPartitionId(),
ReplicaMetricVersionItems.REPLICATION_METRIC_LOG_SIZE
);
+
if(null == metricsResult || metricsResult.failed() || null == metricsResult.getData()) {
continue;
}
@@ -391,6 +396,28 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
return Result.buildSuc(BrokerMetrics.initWithMetric(clusterId, brokerId, metric, logSizeSum));
}
+ private Result getLogSizeFromClient(VersionItemParam metricParam) {
+ BrokerMetricParam param = (BrokerMetricParam)metricParam;
+
+ String metric = param.getMetric();
+ Long clusterId = param.getClusterId();
+ Integer brokerId = param.getBrokerId();
+
+ Result