feat: 健康状态展示优化

This commit is contained in:
GraceWalk
2022-10-28 17:33:41 +08:00
committed by EricZeng
parent 7d045dbf05
commit 5f6df3681c
30 changed files with 757 additions and 650 deletions

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useRef, useState } from 'react';
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer } from 'knowdesign';
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer, Tooltip } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import API from '@src/api';
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
import './index.less';
import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config';
import { healthSorceList, sliderValueMap, sortFieldList, sortTypes, statusFilters } from './config';
import ClusterList from './List';
import AccessClusters from './AccessCluster';
import CustomCheckGroup from './CustomCheckGroup';
import { ClustersPermissionMap } from '../CommonConfig';
import './index.less';
const CheckboxGroup = Checkbox.Group;
const { Option } = Select;
@@ -19,8 +19,17 @@ interface ClustersState {
total: number;
}
interface ClustersHealthState {
deadCount: number;
goodCount: number;
mediumCount: number;
poorCount: number;
total: number;
unknownCount: number;
}
export interface SearchParams {
healthScoreRange?: [number, number];
healthState?: number[];
checkedKafkaVersions?: string[];
sortInfo?: {
sortField: string;
@@ -74,16 +83,24 @@ const MultiClusterPage = () => {
liveCount: 0,
total: 0,
});
const [clustersHealthState, setClustersHealthState] = React.useState<ClustersHealthState>();
const [sliderInfo, setSliderInfo] = React.useState<{
value: [number, number];
desc: string;
}>({
value: [0, 5],
desc: '',
});
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
const [searchParams, setSearchParams] = React.useState<SearchParams>({
keywords: '',
checkedKafkaVersions: [],
healthScoreRange: [0, 100],
sortInfo: {
sortField: 'HealthScore',
sortField: 'HealthState',
sortType: 'asc',
},
clusterStatus: [0, 1],
healthState: [-1, 0, 1, 2, 3],
// 是否拉取当前所有数据
isReloadAll: false,
});
@@ -91,10 +108,28 @@ const MultiClusterPage = () => {
const searchKeyword = useRef('');
const isReload = useRef(false);
const getPhyClusterHealthState = () => {
Utils.request(API.phyClusterHealthState).then((res: ClustersHealthState) => {
setClustersHealthState(res || undefined);
const result: string[] = [];
for (let i = 0; i < sliderInfo.value[1] - sliderInfo.value[0]; i++) {
const val = sliderValueMap[(sliderInfo.value[1] - i) as keyof typeof sliderValueMap];
result.push(`${val.name}: ${res?.[val.key as keyof ClustersHealthState]}`);
}
setSliderInfo((cur) => ({
...cur,
desc: result.reverse().join(', '),
}));
});
};
// 获取集群状态
const getPhyClusterState = () => {
getPhyClusterHealthState();
Utils.request(API.phyClusterState)
.then((res: any) => {
.then((res: ClustersState) => {
setStateInfo(res);
})
.finally(() => {
@@ -111,13 +146,14 @@ const MultiClusterPage = () => {
const updateSearchParams = (params: SearchParams) => {
setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params }));
getPhyClusterHealthState();
};
const searchParamsChangeFunc = {
// 健康分改变
onSilderChange: (value: [number, number]) =>
onSilderChange: (value: number[]) =>
updateSearchParams({
healthScoreRange: value,
healthState: value,
}),
// 排序信息改变
onSortInfoChange: (type: string, value: string) =>
@@ -251,16 +287,41 @@ const MultiClusterPage = () => {
</div>
</div>
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
<h3 className="header-filter-bottom-item-title title-right"></h3>
<div className="header-filter-bottom-item-content">
<Slider
range
step={20}
defaultValue={[0, 100]}
marks={healthSorceList}
onAfterChange={searchParamsChangeFunc.onSilderChange}
/>
</div>
<h3 className="header-filter-bottom-item-title title-right"></h3>
<Tooltip title={sliderInfo.desc} overlayClassName="cluster-health-state-tooltip">
<div className="header-filter-bottom-item-content" id="clusters-slider">
<Slider
dots
range={{ draggableTrack: true }}
step={1}
max={5}
marks={healthSorceList}
value={sliderInfo.value}
tooltipVisible={false}
onChange={(value: [number, number]) => {
if (value[0] !== value[1]) {
const result = [];
for (let i = 0; i < value[1] - value[0]; i++) {
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
result.push(`${val.name}: ${clustersHealthState?.[val.key as keyof ClustersHealthState]}`);
}
setSliderInfo({
value,
desc: result.reverse().join(', '),
});
}
}}
onAfterChange={(value: [number, number]) => {
const result = [];
for (let i = 0; i < value[1] - value[0]; i++) {
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
result.push(val.code);
}
searchParamsChangeFunc.onSilderChange(result);
}}
/>
</div>
</Tooltip>
</div>
</div>
</div>
@@ -269,7 +330,7 @@ const MultiClusterPage = () => {
<div className="multi-cluster-filter-select">
<Select
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortField', value)}
defaultValue="HealthScore"
defaultValue="HealthState"
style={{ width: 170, marginRight: 12 }}
>
{sortFieldList.map((item) => (

View File

@@ -10,15 +10,15 @@ import { timeFormat, oneDayMillims } from '@src/constants/common';
import { IMetricPoint, linesMetric, pointsMetric } from './config';
import { useIntl } from 'react-intl';
import api, { MetricType } from '@src/api';
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
import { ClustersPermissionMap } from '../CommonConfig';
import { getDataUnit } from '@src/constants/chartConfig';
import SmallChart from '@src/components/SmallChart';
import HealthState, { HealthStateEnum } from '@src/components/HealthState';
import { SearchParams } from './HomePage';
const DEFAULT_PAGE_SIZE = 10;
enum ClusterRunState {
export enum ClusterRunState {
Raft = 2,
}
@@ -165,12 +165,9 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
fieldName: 'kafkaVersion',
fieldValueList: searchParams.checkedKafkaVersions as (string | number)[],
},
],
rangeFilterDTOList: [
{
fieldMaxValue: searchParams.healthScoreRange[1],
fieldMinValue: searchParams.healthScoreRange[0],
fieldName: 'HealthScore',
fieldName: 'HealthState',
fieldValueList: searchParams.healthState,
},
],
searchKeywords: searchParams.keywords,
@@ -257,7 +254,7 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
Zookeepers: zks,
HealthCheckPassed: healthCheckPassed,
HealthCheckTotal: healthCheckTotal,
HealthScore: healthScore,
HealthState: healthState,
ZookeepersAvailable: zookeepersAvailable,
LoadReBalanceCpu: loadReBalanceCpu,
LoadReBalanceDisk: loadReBalanceDisk,
@@ -272,28 +269,16 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
history.push(`/cluster/${itemData.id}/cluster`);
}}
>
<div className={'multi-cluster-list-item'}>
<div className="multi-cluster-list-item">
<div className="multi-cluster-list-item-healthy">
<Progress
type="circle"
status={!itemData.alive ? 'exception' : healthScore >= 90 ? 'success' : 'normal'}
strokeWidth={4}
// className={healthScore > 90 ? 'green-circle' : ''}
className={+itemData.alive <= 0 ? 'red-circle' : +healthScore < 90 ? 'blue-circle' : 'green-circle'}
strokeColor={getHealthProcessColor(healthScore, itemData.alive)}
percent={itemData.alive ? healthScore : 100}
format={() => (
<div className={`healthy-percent ${getHealthClassName(healthScore, itemData?.alive)}`}>
{getHealthText(healthScore, itemData?.alive)}
</div>
)}
width={70}
/>
<div className="healthy-degree">
<span className="healthy-degree-status"></span>
<span className="healthy-degree-proportion">
{healthCheckPassed}/{healthCheckTotal}
</span>
<div className="healthy-box">
<HealthState state={healthState} width={70} height={70} />
<div className="healthy-degree">
<span className="healthy-degree-status"></span>
<span className="healthy-degree-proportion">
{healthCheckPassed}/{healthCheckTotal}
</span>
</div>
</div>
</div>
<div className="multi-cluster-list-item-right">

View File

@@ -1,3 +1,4 @@
import { HealthStateEnum } from '@src/components/HealthState';
import { FormItemType, IFormItem } from 'knowdesign/es/extend/x-form';
export const bootstrapServersErrCodes = [10, 11, 12];
@@ -21,8 +22,8 @@ export const sortFieldList = [
value: 'createTime',
},
{
label: '健康',
value: 'HealthScore',
label: '健康状态',
value: 'HealthState',
},
{
label: 'Messages',
@@ -71,18 +72,41 @@ export const metricNameMap = {
[key: string]: string;
};
export const sliderValueMap = {
1: {
code: HealthStateEnum.GOOD,
key: 'goodCount',
name: '好',
},
2: {
code: HealthStateEnum.MEDIUM,
key: 'mediumCount',
name: '中',
},
3: {
code: HealthStateEnum.POOR,
key: 'poorCount',
name: '差',
},
4: {
code: HealthStateEnum.DOWN,
key: 'deadCount',
name: 'Down',
},
5: {
code: HealthStateEnum.UNKNOWN,
key: 'unknownCount',
name: 'Unknown',
},
};
export const healthSorceList = {
0: 0,
10: '',
20: 20,
30: '',
40: 40,
50: '',
60: 60,
70: '',
80: 80,
90: '',
100: 100,
0: '',
1: '',
2: '中',
3: '',
4: 'Down',
5: 'Unknown',
};
export interface IMetricPoint {

View File

@@ -1,5 +1,17 @@
@error-color: #f46a6a;
.cluster-health-state-tooltip {
.dcloud-tooltip-arrow,
.dcloud-tooltip-inner {
margin-bottom: -10px;
}
.dcloud-tooltip-inner {
padding: 2px 4px;
min-height: 25px;
border-radius: 4px;
}
}
.multi-cluster-page {
position: absolute;
left: 0;
@@ -229,6 +241,9 @@
&-slider {
width: 298px;
padding-right: 20px;
.dcloud-slider-mark {
left: -27px;
}
}
&-title {
@@ -404,59 +419,29 @@
transition: 0.5s all;
&-healthy {
display: flex;
align-items: center;
margin-right: 24px;
.dcloud-progress-inner {
border-radius: 50%;
}
.green-circle {
.dcloud-progress-inner {
background: #f5fdfc;
}
}
.red-circle {
.dcloud-progress-inner {
background: #fffafa;
}
}
.healthy-percent {
font-family: DIDIFD-Regular;
font-size: 40px;
text-align: center;
line-height: 36px;
color: #00c0a2;
&.less-90 {
color: @primary-color;
}
&.no-info {
color: #e9e7e7;
}
&.down {
font-family: @font-family-bold;
font-size: 22px;
color: #ff7066;
text-align: center;
line-height: 30px;
}
.healthy-box {
position: relative;
height: 70px;
margin-top: 2px;
}
.healthy-degree {
position: absolute;
bottom: 0;
width: 100%;
font-family: @font-family-bold;
font-size: 12px;
color: #495057;
line-height: 15px;
margin-top: 6px;
line-height: 1;
text-align: center;
&-status {
margin-right: 6px;
color: #74788d;
}
&-proportion {
}
}
}