初始化3.0.0版本

This commit is contained in:
zengqiao
2022-08-18 17:04:05 +08:00
parent 462303fca0
commit 51832385b1
2446 changed files with 93177 additions and 127211 deletions

View File

@@ -0,0 +1,98 @@
import { getBasicChartConfig } from '@src/constants/chartConfig';
import moment from 'moment';
const DEFAULT_METRIC = 'MessagesIn';
// 图表 tooltip 展示的样式
const messagesInTooltipFormatter = (date: any, arr: any) => {
// 面积图只有一条线,这里直接取 arr 的第 0 项
const params = arr[0];
// MessageIn 的指标数据存放在 data 数组第 3 项
const metricsData = params.data[2];
const str = `<div style="margin: 3px 0;">
<div style="display:flex;align-items:center;">
<div style="margin-right:4px;width:8px;height:2px;background-color:${params.color};"></div>
<div style="flex:1;display:flex;justify-content:space-between;align-items:center;overflow: hidden;">
<span style="flex: 1;font-size:12px;color:#74788D;pointer-events:auto;margin-left:2px;line-height: 18px;font-family: HelveticaNeue;overflow: hidden; text-overflow: ellipsis; white-space: no-wrap;">
${params.seriesName}
</span>
<span style="font-size:12px;color:#212529;line-height:18px;font-family:HelveticaNeue-Medium;margin-left: 10px;">
${parseFloat(Number(params.value[1]).toFixed(3))}
<span style="font-family: PingFangSC-Regular;color: #495057;">${metricsData[DEFAULT_METRIC]?.unit || ''}</span>
</span>
</div>
</div>
</div>`;
return `<div style="margin: 0px 0 0; position: relative; z-index: 99;width: fit-content;">
<div style="padding: 8px 0;height: 100%;">
<div style="font-size:12px;padding: 0 12px;color:#212529;line-height:20px;font-family: HelveticaNeue;">
${date}
</div>
<div style="margin: 4px 0 0 0;padding: 0 12px;">
${str}
<div style="width: 100%; height: 1px; background: #EFF2F7;margin: 8px 0;"></div>
${metricsData
.map(({ key, value, unit }: { key: string; value: number; unit: string }) => {
if (key === DEFAULT_METRIC) return '';
return `
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-size:12px;color:#74788D;pointer-events:auto;margin-left:2px;line-height: 18px;font-family: HelveticaNeue;margin-right: 10px;">
${key}
</span>
<span style="font-size:12px;color:#212529;pointer-events:auto;margin-left:2px;line-height: 18px;font-family: HelveticaNeue;">
${parseFloat(Number(value).toFixed(3))}
<span style="font-family: PingFangSC-Regular;color: #495057;">${unit}</span>
</span>
</div>
`;
})
.join('')}
</div>
</div>
</div>`;
};
export const getChartConfig = (props: any) => {
const { metricName, lineColor, isDefaultMetric = false } = props;
return {
option: getBasicChartConfig({
// TODO: time 轴图表联动有问题,先切换为 category
// xAxis: { type: 'time', boundaryGap: isDefaultMetric ? ['2%', '2%'] : ['5%', '5%'] },
title: { show: false },
legend: { show: false },
grid: { top: 24, bottom: 12 },
lineColor: [lineColor],
tooltip: isDefaultMetric
? {
formatter: function (params: any) {
let res = '';
if (params != null && params.length > 0) {
res += messagesInTooltipFormatter(moment(Number(params[0].axisValue)).format('YYYY-MM-DD HH:mm'), params);
}
return res;
},
}
: {},
}),
seriesCallback: (lineList: { name: string; data: [number, string | number][] }[]) => {
// 补充线条配置
return lineList.map((line) => {
return {
...line,
lineStyle: {
width: 1,
},
symbol: 'emptyCircle',
symbolSize: 4,
// 面积图样式
areaStyle: {
color: lineColor,
opacity: 0.06,
},
};
});
},
};
};

View File

@@ -0,0 +1,135 @@
.cluster-container-border {
background: #ffffff;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.01), 0 3px 6px 3px rgba(0, 0, 0, 0.01), 0 2px 6px 0 rgba(0, 0, 0, 0.03);
border-radius: 12px;
}
.cluster-detail-container {
width: 100%;
&-header {
display: flex;
align-items: center;
height: 36px;
.refresh-icon-box {
display: flex;
justify-content: center;
align-items: center;
width: 22px;
height: 22px;
border-radius: 50%;
cursor: pointer;
.refresh-icon {
font-size: 14px;
color: #74788d;
}
&:hover {
background: #21252904;
.refresh-icon {
color: #495057;
}
}
}
}
&-main {
.header-chart-container {
&-loading {
display: flex;
justify-content: center;
align-items: center;
}
width: 100%;
height: 244px;
margin-bottom: 12px;
.cluster-container-border();
}
.content {
display: flex;
width: 100%;
height: calc(100vh - 404px);
min-height: 526px;
.multiple-chart-container {
flex: 1;
height: 100%;
overflow: hidden;
.cluster-container-border();
> div {
width: 100%;
height: 100%;
padding: 16px;
overflow: hidden auto;
}
&-loading {
display: flex;
justify-content: center;
align-items: center;
}
.chart-box {
position: relative;
width: 100%;
height: 244px;
background: #f8f9fa;
border-radius: 8px;
.expand-icon-box {
position: absolute;
z-index: 1000;
top: 14px;
right: 16px;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 16px;
text-align: center;
border-radius: 50%;
transition: background-color 0.3s ease;
.expand-icon {
color: #adb5bc;
line-height: 24px;
}
&:hover {
background: rgba(33, 37, 41, 0.06);
.expand-icon {
color: #74788d;
}
}
}
}
}
.config-change-records-container {
width: 240px;
height: 100%;
margin-left: 12px;
.cluster-container-border();
}
}
}
.chart-box-title {
padding: 18px 0 0 20px;
font-family: @font-family-bold;
line-height: 16px;
.name {
font-size: 14px;
color: #212529;
}
.unit {
font-size: 12px;
color: #495057;
}
> span {
cursor: pointer;
}
}
}

View File

@@ -0,0 +1,452 @@
import { Col, Row, SingleChart, IconFont, Utils, Modal, Spin, Empty, AppContainer, Tooltip } from 'knowdesign';
import React, { useEffect, useRef, useState } from 'react';
import api from '@src/api';
import { getChartConfig } from './config';
import './index.less';
import { useParams } from 'react-router-dom';
import {
MetricDefaultChartDataType,
MetricChartDataType,
formatChartData,
supplementaryPoints,
} from '@src/components/DashboardDragChart/config';
import { MetricType } from '@src/api';
import { getDataNumberUnit, getUnit } from '@src/constants/chartConfig';
import SingleChartHeader, { KsHeaderOptions } from '@src/components/SingleChartHeader';
import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common';
type ChartFilterOptions = Omit<KsHeaderOptions, 'gridNum'>;
interface MetricInfo {
type: number;
name: string;
desc: string;
set: boolean;
support: boolean;
}
interface MessagesInDefaultData {
aggType: string | null;
createTime: string | null;
updateTime: string | null;
timeStamp: number;
values: {
[metric: string]: string;
};
}
type MessagesInMetric = {
name: 'MessagesIn';
unit: string;
data: (readonly [number, number | string, { key: string; value: number; unit: string }[]])[];
};
const { EventBus } = Utils;
const busInstance = new EventBus();
// 图表颜色定义 & 计算
const CHART_LINE_COLORS = ['#556EE6', '#3991FF'];
const calculateChartColor = (i: number) => {
const isEvenRow = ((i / 2) | 0) % 2;
const isEvenCol = i % 2;
return CHART_LINE_COLORS[isEvenRow ^ isEvenCol];
};
const DEFAULT_METRIC = 'MessagesIn';
// 默认指标图表固定需要获取展示的指标项
const DEFUALT_METRIC_NEED_METRICS = [DEFAULT_METRIC, 'TotalLogSize', 'TotalProduceRequests', 'Topics', 'Partitions'];
const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
const [global] = AppContainer.useGlobalValue();
const { clusterId } = useParams<{ clusterId: string }>();
const [metricList, setMetricList] = useState<MetricInfo[]>([]); // 指标列表
const [selectedMetricNames, setSelectedMetricNames] = useState<(string | number)[]>([]); // 默认选中的指标的列表
const [metricDataList, setMetricDataList] = useState<any>([]);
const [messagesInMetricData, setMessagesInMetricData] = useState<MessagesInMetric>({
name: 'MessagesIn',
unit: '',
data: [],
});
const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>();
const [defaultChartLoading, setDefaultChartLoading] = useState<boolean>(true);
const [chartLoading, setChartLoading] = useState<boolean>(true);
const [showChartDetailModal, setShowChartDetailModal] = useState<boolean>(false);
const [chartDetail, setChartDetail] = useState<any>();
const curFetchingTimestamp = useRef({
messagesIn: 0,
other: 0,
});
// 筛选项变化或者点击刷新按钮
const ksHeaderChange = (ksOptions: KsHeaderOptions) => {
// 如果为相对时间,则当前时间减去 1 分钟,避免最近一分钟的数据还没采集到时前端多补一个点
if (ksOptions.isRelativeRangeTime) {
ksOptions.rangeTime = ksOptions.rangeTime.map((timestamp) => timestamp - 60 * 1000) as [number, number];
}
setCurHeaderOptions({
isRelativeRangeTime: ksOptions.isRelativeRangeTime,
isAutoReload: ksOptions.isAutoReload,
rangeTime: ksOptions.rangeTime,
});
};
// 获取指标列表
const getMetricList = () => {
Utils.request(api.getDashboardMetricList(clusterId, MetricType.Cluster)).then((res: MetricInfo[] | null) => {
if (!res) return;
const showMetrics = res.filter((metric) => metric.support);
const selectedMetrics = showMetrics.filter((metric) => metric.set).map((metric) => metric.name);
!selectedMetrics.includes(DEFAULT_METRIC) && selectedMetrics.push(DEFAULT_METRIC);
setMetricList(showMetrics);
setSelectedMetricNames(selectedMetrics);
});
};
// 更新指标
const updateMetricList = (metricsSet: { [name: string]: boolean }) => {
return Utils.request(api.getDashboardMetricList(clusterId, MetricType.Cluster), {
method: 'POST',
data: {
metricsSet,
},
});
};
// 指标选中项更新回调
const indicatorChangeCallback = (newMetricNames: (string | number)[]) => {
const updateMetrics: { [name: string]: boolean } = {};
// 需要选中的指标
newMetricNames.forEach((name) => !selectedMetricNames.includes(name) && (updateMetrics[name] = true));
// 取消选中的指标
selectedMetricNames.forEach((name) => !newMetricNames.includes(name) && (updateMetrics[name] = false));
const requestPromise = Object.keys(updateMetrics).length ? updateMetricList(updateMetrics) : Promise.resolve();
requestPromise.then(
() => getMetricList(),
() => getMetricList()
);
return requestPromise;
};
// 获取 metric 列表的图表数据
const getMetricData = () => {
if (!selectedMetricNames.length) return;
!curHeaderOptions.isAutoReload && setChartLoading(true);
const [startTime, endTime] = curHeaderOptions.rangeTime;
const curTimestamp = Date.now();
curFetchingTimestamp.current = {
...curFetchingTimestamp.current,
messagesIn: curTimestamp,
};
Utils.request(api.getClusterMetricDataList(), {
method: 'POST',
data: {
startTime,
endTime,
clusterPhyIds: [clusterId],
metricsNames: selectedMetricNames.filter((name) => name !== DEFAULT_METRIC),
},
}).then(
(res: MetricDefaultChartDataType[]) => {
// 如果当前请求不是最新请求,则不做任何操作
if (curFetchingTimestamp.current.messagesIn !== curTimestamp) {
return;
}
const supplementaryInterval = (endTime - startTime > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
const formattedMetricData: MetricChartDataType[] = formatChartData(
res,
global.getMetricDefine || {},
MetricType.Cluster,
curHeaderOptions.rangeTime,
supplementaryInterval
);
formattedMetricData.forEach((data) => (data.metricLines[0].name = data.metricName));
setMetricDataList(formattedMetricData);
setChartLoading(false);
},
() => setChartLoading(false)
);
};
// 获取默认展示指标的图表数据
const getDefaultMetricData = () => {
!curHeaderOptions.isAutoReload && setDefaultChartLoading(true);
const curTimestamp = Date.now();
curFetchingTimestamp.current = {
...curFetchingTimestamp.current,
other: curTimestamp,
};
Utils.request(api.getClusterDefaultMetricData(), {
method: 'POST',
data: {
startTime: curHeaderOptions.rangeTime[0],
endTime: curHeaderOptions.rangeTime[1],
clusterPhyIds: [clusterId],
metricsNames: DEFUALT_METRIC_NEED_METRICS,
},
}).then(
(res: MessagesInDefaultData[]) => {
// 如果当前请求不是最新请求,则不做任何操作
if (curFetchingTimestamp.current.other !== curTimestamp) {
return;
}
// TODO: 这里直接将指标数据放到数组第三项中,之后可以尝试优化,优化需要注意 tooltipFormatter 函数也要修改
let maxValue = -1;
const result = res.map((item) => {
const { timeStamp, values } = item;
let parsedValue: string | number = Number(values.MessagesIn);
if (Number.isNaN(parsedValue)) {
parsedValue = values.MessagesIn;
} else {
if (maxValue < parsedValue) maxValue = parsedValue;
}
const valuesWithUnit = Object.entries(values).map(([key, value]) => {
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));
unit = unit.toLowerCase().replace('byte', unitName);
valueWithUnit /= unitSize;
}
const returnValue = {
key,
value: valueWithUnit,
unit,
};
return returnValue;
});
return [timeStamp, values.MessagesIn || '0', valuesWithUnit] as [number, number | string, typeof valuesWithUnit];
});
result.sort((a, b) => (a[0] as number) - (b[0] as number));
const line = {
name: 'MessagesIn' as const,
unit: global.getMetricDefine(MetricType.Cluster, 'MessagesIn')?.unit,
data: result as any,
};
if (maxValue > 0) {
const [unitName, unitSize]: [string, number] = getDataNumberUnit(maxValue);
line.unit = `${unitName}${line.unit}`;
result.forEach((point) => ((point[1] as number) /= unitSize));
}
// 补充缺少的图表点
const extraMetrics = result[0][2].map((info) => ({
...info,
value: 0,
}));
const supplementaryInterval =
(curHeaderOptions.rangeTime[1] - curHeaderOptions.rangeTime[0] > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
supplementaryPoints([line], curHeaderOptions.rangeTime, supplementaryInterval, (point) => {
point.push(extraMetrics as any);
return point;
});
setMessagesInMetricData(line);
setDefaultChartLoading(false);
},
() => setDefaultChartLoading(false)
);
};
// 监听盒子宽度变化,重置图表宽度
const observeDashboardWidthChange = () => {
const targetNode = document.getElementsByClassName('dcd-two-columns-layout-sider-footer')[0];
targetNode && targetNode.addEventListener('click', () => busInstance.emit('chartResize'));
};
useEffect(() => {
getMetricData();
}, [selectedMetricNames]);
useEffect(() => {
if (curHeaderOptions && curHeaderOptions?.rangeTime.join(',') !== '0,0') {
getDefaultMetricData();
getMetricData();
}
}, [curHeaderOptions]);
useEffect(() => {
getMetricList();
setTimeout(() => observeDashboardWidthChange());
}, []);
return (
<div className="cluster-detail-container">
<SingleChartHeader
onChange={ksHeaderChange}
hideNodeScope={true}
hideGridSelect={true}
indicatorSelectModule={{
hide: false,
metricType: MetricType.Cluster,
tableData: metricList,
selectedRows: selectedMetricNames,
checkboxProps: (record: MetricInfo) => {
return record.name === DEFAULT_METRIC
? {
disabled: true,
}
: {};
},
submitCallback: indicatorChangeCallback,
}}
/>
<div className="cluster-detail-container-main">
{/* MessageIn 图表 */}
<div className={`header-chart-container ${!messagesInMetricData.data.length ? 'header-chart-container-loading' : ''}`}>
<Spin spinning={defaultChartLoading}>
{/* TODO: 暂时通过判断是否有图表数据来修复,有时间可以查找下宽度溢出的原因 */}
{messagesInMetricData.data.length ? (
<>
<div className="chart-box-title">
<Tooltip
placement="topLeft"
title={() => {
let content = '';
const metricDefine = global.getMetricDefine(MetricType.Cluster, messagesInMetricData.name);
if (metricDefine) {
content = metricDefine.desc;
}
return content;
}}
>
<span>
<span className="name">{messagesInMetricData.name}</span>
<span className="unit">{messagesInMetricData.unit}</span>
</span>
</Tooltip>
</div>
<SingleChart
chartKey="messagesIn"
chartTypeProp="line"
showHeader={false}
wrapStyle={{
width: 'auto',
height: 210,
}}
connectEventName="clusterChart"
eventBus={busInstance}
propChartData={[messagesInMetricData]}
{...getChartConfig({
// metricName: `${messagesInMetricData.name}{unit|${messagesInMetricData.unit}}`,
lineColor: CHART_LINE_COLORS[0],
isDefaultMetric: true,
})}
/>
</>
) : (
''
)}
</Spin>
</div>
<div className="content">
{/* 其余指标图表 */}
<div className="multiple-chart-container">
<div className={!metricDataList.length ? 'multiple-chart-container-loading' : ''}>
<Spin spinning={chartLoading}>
<Row gutter={[16, 16]}>
{metricDataList.length ? (
metricDataList.map((data: any, i: number) => {
const { metricName, metricUnit, metricLines } = data;
return (
<Col key={metricName} span={12}>
<div className="chart-box">
<div className="chart-box-title">
<Tooltip
placement="topLeft"
title={() => {
let content = '';
const metricDefine = global.getMetricDefine(MetricType.Cluster, metricName);
if (metricDefine) {
content = metricDefine.desc;
}
return content;
}}
>
<span>
<span className="name">{metricName}</span>
<span className="unit">{metricUnit}</span>
</span>
</Tooltip>
</div>
<div
className="expand-icon-box"
onClick={() => {
setChartDetail(data);
setShowChartDetailModal(true);
}}
>
<IconFont type="icon-chuangkoufangda" className="expand-icon" />
</div>
<SingleChart
chartKey={metricName}
showHeader={false}
chartTypeProp="line"
wrapStyle={{
width: 'auto',
height: 210,
}}
connectEventName="clusterChart"
eventBus={busInstance}
propChartData={metricLines}
{...getChartConfig({
metricName: `${metricName}{unit|${metricUnit}}`,
lineColor: calculateChartColor(i),
})}
/>
</div>
</Col>
);
})
) : chartLoading ? (
<></>
) : (
<Empty description="请先选择指标或刷新" style={{ width: '100%', height: '100%' }} />
)}
</Row>
</Spin>
</div>
</div>
{/* 历史配置变更记录内容 */}
<div className="config-change-records-container">{props.children}</div>
</div>
</div>
{/* 图表详情 */}
<Modal
width={1080}
visible={showChartDetailModal}
centered={true}
footer={null}
closable={false}
onCancel={() => setShowChartDetailModal(false)}
>
<div className="chart-detail-modal-container">
<div className="expand-icon-box" onClick={() => setShowChartDetailModal(false)}>
<IconFont type="icon-chuangkousuoxiao" className="expand-icon" />
</div>
{chartDetail && (
<SingleChart
chartTypeProp="line"
wrapStyle={{
width: 'auto',
height: 462,
}}
propChartData={chartDetail.metricLines}
{...getChartConfig({
metricName: `${chartDetail.metricName}{unit|${chartDetail.metricUnit}}`,
})}
/>
)}
</div>
</Modal>
</div>
);
};
export default DetailChart;