diff --git a/km-console/packages/layout-clusters-fe/src/api/index.ts b/km-console/packages/layout-clusters-fe/src/api/index.ts index bbf11d0a..0dcc912a 100755 --- a/km-console/packages/layout-clusters-fe/src/api/index.ts +++ b/km-console/packages/layout-clusters-fe/src/api/index.ts @@ -24,6 +24,7 @@ const api = { logout: `${securityPrefix}/account/logout`, // 全局信息 + getVersionInfo: () => getApi('/self/version'), getUserInfo: (userId: number) => `${securityPrefix}/user/${userId}`, getPermissionTree: `${securityPrefix}/permission/tree`, getKafkaVersionItems: () => getApi('/kafka-versions-items'), @@ -60,6 +61,7 @@ const api = { phyClustersDashbord: getApi(`/physical-clusters/dashboard`), supportKafkaVersion: getApi(`/support-kafka-versions`), phyClusterState: getApi(`/physical-clusters/state`), + phyClusterHealthState: getApi(`/physical-clusters/health-state`), getOperatingStateList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/groups-overview`), getGroupTopicList: (clusterPhyId: number, groupName: string) => getApi(`/clusters/${clusterPhyId}/groups/${groupName}/topics-overview`), @@ -201,6 +203,14 @@ const api = { getJobsTaskData: (clusterPhyId: string, jobId: string | number) => getApi(`/clusters/${clusterPhyId}/jobs/${jobId}/modify-detail`), //编辑任务 putJobsTaskData: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/jobs`), + + // Zookeeper 接口 + getZookeeperState: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeepers-state`), + getZookeeperList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeepers-overview`), + getZookeeperNodeChildren: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-children`), + getZookeeperNodeData: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-data`), + getZookeeperMetricsInfo: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeeper-latest-metrics`), + getZookeeperMetrics: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeeper-metrics`), }; export default api; diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-status-exception.png b/km-console/packages/layout-clusters-fe/src/assets/health-down-emoji.png similarity index 100% rename from km-console/packages/layout-clusters-fe/src/assets/health-status-exception.png rename to km-console/packages/layout-clusters-fe/src/assets/health-down-emoji.png diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-down.png b/km-console/packages/layout-clusters-fe/src/assets/health-down.png new file mode 100644 index 00000000..4a21508e Binary files /dev/null and b/km-console/packages/layout-clusters-fe/src/assets/health-down.png differ diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-status-success.png b/km-console/packages/layout-clusters-fe/src/assets/health-good-emoji.png similarity index 100% rename from km-console/packages/layout-clusters-fe/src/assets/health-status-success.png rename to km-console/packages/layout-clusters-fe/src/assets/health-good-emoji.png diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-good.png b/km-console/packages/layout-clusters-fe/src/assets/health-good.png new file mode 100644 index 00000000..b9aa8a44 Binary files /dev/null and b/km-console/packages/layout-clusters-fe/src/assets/health-good.png differ diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-status-normal.png b/km-console/packages/layout-clusters-fe/src/assets/health-medium-emoji.png similarity index 100% rename from km-console/packages/layout-clusters-fe/src/assets/health-status-normal.png rename to km-console/packages/layout-clusters-fe/src/assets/health-medium-emoji.png diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-medium.png b/km-console/packages/layout-clusters-fe/src/assets/health-medium.png new file mode 100644 index 00000000..1f2de005 Binary files /dev/null and b/km-console/packages/layout-clusters-fe/src/assets/health-medium.png differ diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-poor-emoji.png b/km-console/packages/layout-clusters-fe/src/assets/health-poor-emoji.png new file mode 100644 index 00000000..fd04e5c0 Binary files /dev/null and b/km-console/packages/layout-clusters-fe/src/assets/health-poor-emoji.png differ diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-poor.png b/km-console/packages/layout-clusters-fe/src/assets/health-poor.png new file mode 100644 index 00000000..a3562ab1 Binary files /dev/null and b/km-console/packages/layout-clusters-fe/src/assets/health-poor.png differ diff --git a/km-console/packages/layout-clusters-fe/src/assets/health-unknown.png b/km-console/packages/layout-clusters-fe/src/assets/health-unknown.png new file mode 100644 index 00000000..a71759ae Binary files /dev/null and b/km-console/packages/layout-clusters-fe/src/assets/health-unknown.png differ diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerDetailHealthCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerDetailHealthCheck.tsx index 6d3513ed..132ca8a4 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerDetailHealthCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerDetailHealthCheck.tsx @@ -1,11 +1,12 @@ /* eslint-disable react/display-name */ import React, { useState, useEffect } from 'react'; -import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import CardBar from '@src/components/CardBar'; import { healthDataProps } from '.'; -import { Tag, Utils } from 'knowdesign'; +import { Utils } from 'knowdesign'; import Api from '@src/api'; import { hashDataParse } from '@src/constants/common'; +import { HealthStateEnum } from '../HealthState'; export default (props: { record: any }) => { const { record } = props; @@ -14,22 +15,20 @@ export default (props: { record: any }) => { const [loading, setLoading] = useState(false); const [cardData, setCardData] = useState([]); const [healthData, setHealthData] = useState({ - score: 0, + state: HealthStateEnum.UNKNOWN, passed: 0, total: 0, - alive: 0, }); - const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'live']; + useEffect(() => { setLoading(true); Utils.post(Api.getBrokerDetailMetricPoints(hashDataParse(urlLocation.hash)?.brokerId, urlParams?.clusterId), [ 'Partitions', 'Leaders', 'PartitionURP', - 'HealthScore', 'HealthCheckPassed', 'HealthCheckTotal', - 'Alive', + 'HealthState', ]).then((data: any) => { setLoading(false); const rightData = JSON.parse(JSON.stringify(data.metrics)); @@ -47,14 +46,12 @@ export default (props: { record: any }) => { value: rightData['PartitionURP'] || '-', }, ]; - const healthResData: any = {}; - healthResData.score = data?.metrics?.['HealthScore'] || 0; - healthResData.passed = data?.metrics?.['HealthCheckPassed'] || 0; - healthResData.total = data?.metrics?.['HealthCheckTotal'] || 0; - healthResData.alive = data?.metrics?.['Alive'] || 0; setCardData(cordRightMap); - setHealthData(healthResData); - // setCardData(data.metrics) + setHealthData({ + state: data?.metrics?.['HealthState'], + passed: data?.metrics?.['HealthCheckPassed'] || 0, + total: data?.metrics?.['HealthCheckTotal'] || 0, + }); }); }, []); return ( diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx index e4bc6c2b..31531c15 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/BrokerHealthCheck.tsx @@ -6,6 +6,7 @@ import { healthDataProps } from '.'; import { Tag, Tooltip, Utils } from 'knowdesign'; import api from '@src/api'; import { QuestionCircleOutlined } from '@ant-design/icons'; +import { HealthStateEnum } from '../HealthState'; export default () => { const routeParams = useParams<{ @@ -14,26 +15,21 @@ export default () => { const [loading, setLoading] = useState(false); const [cardData, setCardData] = useState([]); const [healthData, setHealthData] = useState({ - score: 0, + state: HealthStateEnum.UNKNOWN, passed: 0, total: 0, - alive: 0, }); - const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize']; - const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'Alive']; + const healthItems = ['HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'HealthState']; + useEffect(() => { setLoading(true); // 获取左侧健康度 const brokerMetric = Utils.post(api.getBrokerMetricPoints(Number(routeParams.clusterId)), healthItems).then((data: any) => { - const healthResData: any = {}; - // healthResData.score = data?.find((item:any) => item.metricName === 'HealthScore_Brokers')?.value || 0; - // healthResData.passed = data?.find((item:any) => item.metricName === 'HealthCheckPassed_Brokers')?.value || 0; - // healthResData.total = data?.find((item:any) => item.metricName === 'HealthCheckTotal_Brokers')?.value || 0; - healthResData.score = data?.metrics?.['HealthScore_Brokers'] || 0; - healthResData.passed = data?.metrics?.['HealthCheckPassed_Brokers'] || 0; - healthResData.total = data?.metrics?.['HealthCheckTotal_Brokers'] || 0; - healthResData.alive = data?.metrics?.['Alive'] || 0; - setHealthData(healthResData); + setHealthData({ + state: data?.metrics?.['HealthState'], + passed: data?.metrics?.['HealthCheckPassed_Brokers'] || 0, + total: data?.metrics?.['HealthCheckTotal_Brokers'] || 0, + }); }); // 获取右侧状态 const brokersState = Utils.request(api.getBrokersState(routeParams?.clusterId)).then((data) => { @@ -115,6 +111,6 @@ export default () => { setLoading(false); }); }, [routeParams.clusterId]); - // console.log('cardData', cardData, healthData); + return ; }; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/ConsumerGroupHealthCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/ConsumerGroupHealthCheck.tsx index 6df2f2d8..99719e2c 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/ConsumerGroupHealthCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/ConsumerGroupHealthCheck.tsx @@ -4,6 +4,7 @@ import CardBar from '@src/components/CardBar'; import { healthDataProps } from '.'; import { Utils } from 'knowdesign'; import api from '@src/api'; +import { HealthStateEnum } from '../HealthState'; export default () => { const routeParams = useParams<{ @@ -12,22 +13,17 @@ export default () => { const [loading, setLoading] = useState(false); const [cardData, setCardData] = useState([]); const [healthData, setHealthData] = useState({ - score: 0, + state: HealthStateEnum.UNKNOWN, passed: 0, total: 0, - alive: 0, }); - const [healthDetail, setHealthDetail] = useState([]); const cardItems = ['Groups', 'GroupActives', 'GroupEmptys', 'GroupRebalances', 'GroupDeads']; - const healthItems = ['HealthScore_Groups', 'HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'Alive']; + const healthItems = ['HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'HealthState']; + useEffect(() => { setLoading(true); Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => { setLoading(false); - // setCardData(data - // .filter((item: any) => cardItems.indexOf(item.metricName) >= 0) - // .map((item: any) => ({ title: item.metricName, value: item.value })) - // ) setCardData( cardItems.map((item) => { if (item === 'GroupDeads') { @@ -36,12 +32,11 @@ export default () => { return { title: item, value: data.metrics[item] }; }) ); - const healthResData: any = {}; - healthResData.score = data.metrics['HealthScore_Groups'] || 0; - healthResData.passed = data.metrics['HealthCheckPassed_Groups'] || 0; - healthResData.total = data.metrics['HealthCheckTotal_Groups'] || 0; - healthResData.alive = data.metrics['Alive'] || 0; - setHealthData(healthResData); + setHealthData({ + state: data?.metrics?.['HealthState'], + passed: data?.metrics?.['HealthCheckPassed_Groups'] || 0, + total: data?.metrics?.['HealthCheckTotal_Groups'] || 0, + }); }); }, []); return ; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/JobsCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/JobsCheck.tsx index 57b4b5a3..107f8288 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/JobsCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/JobsCheck.tsx @@ -2,8 +2,7 @@ import React, { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import CardBar from '@src/components/CardBar'; -import { healthDataProps } from '.'; -import { Tag, Utils } from 'knowdesign'; +import { Utils } from 'knowdesign'; import Api from '@src/api'; export default () => { @@ -12,14 +11,7 @@ export default () => { }>(); const [loading, setLoading] = useState(false); const [cardData, setCardData] = useState([]); - const [healthData, setHealthData] = useState({ - score: 0, - passed: 0, - total: 0, - alive: 0, - }); - const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize']; - const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'alive']; + const getCordRightMap = (data: any) => { const cordRightMap = [ { @@ -49,6 +41,7 @@ export default () => { ]; return cordRightMap; }; + useEffect(() => { setLoading(true); // 获取状态 diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/LoadRebalanceCardBar.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/LoadRebalanceCardBar.tsx index fff750e1..eeda7c3d 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/LoadRebalanceCardBar.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/LoadRebalanceCardBar.tsx @@ -32,7 +32,6 @@ const LoadRebalanceCardBar = (props: any) => { const [loading, setLoading] = useState(false); const [cardData, setCardData] = useState([]); const [normsVisible, setNormsVisible] = useState(null); - const cardItems = ['AclEnable', 'Acls', 'AclUsers', 'AclTopics', 'AclGroups']; const onClose = () => { setNormsVisible(false); }; @@ -45,11 +44,9 @@ const LoadRebalanceCardBar = (props: any) => { // 获取右侧状态 getCartInfo() .then((res: any) => { - // const { AclEnable, Acls, AclUsers, AclTopics, AclGroups } = res.metrics; const { next, sub, status } = res; const { cpu, disk, bytesIn, bytesOut } = sub; const newNextDate: any = transUnitTimePro(moment(next).valueOf() - moment().valueOf()); - // const newNextDate = parseInt(`${transUnitTimePro(moment(next).valueOf() - moment().valueOf())}`); const cardMap = [ { title() { @@ -80,20 +77,15 @@ const LoadRebalanceCardBar = (props: any) => { > {!status ? '已均衡' : '未均衡'} - {/* 已均衡 */}
周期均衡 - {/* - 周期均衡 - */} 距下次均衡还剩{newNextDate?.value || 0} {newNextDate?.unit || '分钟'} - {/* {距下次均衡还剩{1}小时} */}
); @@ -106,73 +98,6 @@ const LoadRebalanceCardBar = (props: any) => { padding: '12px 12px 8px 12px', }, }, - // { - // // title: 'CPU avg', - // title() { - // return ( - //
- // CPU AVG - // {!cpu?.interval && cpu?.interval !== 0 && ( - // - // - // - // )} - // {/* setNormsVisible(true)} type="icon-shezhi"> */} - //
- // ); - // }, - // value(visibleType: boolean) { - // return ( - //
- //
- //
- // {cpu?.avg || 0} - // % - //
- //
- // 均衡区间: ±{cpu?.interval || 0}% - //
- //
- // - //
- // - // 超过均衡区间的有: {cpu?.bigNu || 0} - //
- //
- // - // 在均衡区间内的有: {cpu?.betweenNu || 0} - //
- //
- // - // 低于均衡区间的有: {cpu?.smallNu || 0} - //
- //
- // } - // getPopupContainer={(triggerNode: any) => { - // return triggerNode; - // }} - // color="#ffffff" - // > - //
- // - //
- // - // - // ); - // }, - // className: 'custom-card-bar', - // valueClassName: 'custom-card-bar-value', - // }, { title() { return ( diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicDetailHealthCheck.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicDetailHealthCheck.tsx index 5cfcf236..80191c48 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicDetailHealthCheck.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/TopicDetailHealthCheck.tsx @@ -5,8 +5,10 @@ import { healthDataProps } from '.'; import { Utils } from 'knowdesign'; import { IconFont } from '@knowdesign/icons'; import api from '@src/api'; -import { healthScoreCondition } from './const'; import { hashDataParse } from '@src/constants/common'; +import { HealthStateEnum } from '../HealthState'; + +const healthItems = ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState']; const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => { return typeof v === 'function' ? v(visibleType) : v; @@ -19,14 +21,12 @@ export default (props: { record: any }) => { const [loading, setLoading] = useState(false); const [cardData, setCardData] = useState([]); const [healthData, setHealthData] = useState({ - score: 0, + state: HealthStateEnum.UNKNOWN, passed: 0, total: 0, - alive: 0, }); - const [healthDetail, setHealthDetail] = useState([]); const [clusterAlive, setClusterAlive] = useState(0); - const healthItems = ['HealthScore', 'HealthCheckPassed', 'HealthCheckTotal', 'alive']; + const getNumAndSubTitles = (cardColumnsItemData: any) => { return (
@@ -40,21 +40,21 @@ export default (props: { record: any }) => {
); }; + useEffect(() => { setLoading(true); const topicName = hashDataParse(location.hash)['topicName']; - let detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then( + const detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then( (data: any) => { - let healthResData: any = {}; - healthResData.score = data.metrics['HealthScore'] || 0; - healthResData.passed = data.metrics['HealthCheckPassed'] || 0; - healthResData.total = data.metrics['HealthCheckTotal'] || 0; - // healthResData.alive = data.metrics['alive'] || 0 - setHealthData(healthResData); + setHealthData({ + state: data.metrics['HealthState'], + passed: data.metrics['HealthCheckPassed'] || 0, + total: data.metrics['HealthCheckTotal'] || 0, + }); } ); - let detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => { + const detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => { setCardData([ { title: 'Partitions', @@ -87,13 +87,12 @@ export default (props: { record: any }) => { ]); }); // 获取集群维度的指标信息 - let clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then( + const clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then( (clusterHealthState: any) => { - let clusterAlive = clusterHealthState?.metrics?.Alive || 0; - setClusterAlive(clusterAlive); + setClusterAlive(clusterHealthState?.metrics?.Alive || 0); } ); - Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then((res) => { + Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then(() => { setLoading(false); }); }, []); @@ -101,7 +100,7 @@ export default (props: { record: any }) => { { const routeParams = useParams<{ @@ -12,14 +13,12 @@ export default () => { const [loading, setLoading] = useState(false); const [cardData, setCardData] = useState([]); const [healthData, setHealthData] = useState({ - score: 0, + state: HealthStateEnum.UNKNOWN, passed: 0, total: 0, - alive: 0, }); - const [healthDetail, setHealthDetail] = useState([]); const cardItems = ['Topics', 'Partitions', 'PartitionNoLeader', 'PartitionMinISR_S', 'PartitionMinISR_E', 'PartitionURP']; - const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'Alive']; + const healthItems = ['HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'HealthState']; useEffect(() => { setLoading(true); Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => { @@ -42,12 +41,6 @@ export default () => { PartitionURP: 'URP', PartitionNoLeader: 'No Leader', }; - // setCardData(data - // .filter(item => cardItems.indexOf(item.name) >= 0) - // .map(item => { - // return { title: metricElmMap[item.name] || item.name, value: item.value } - // }) - // ) setCardData( cardItems.map((item) => { let title = item; @@ -66,12 +59,11 @@ export default () => { return { title, value: data.metrics[item] }; }) ); - const healthResData: any = {}; - healthResData.score = data.metrics['HealthScore_Topics'] || 0; - healthResData.passed = data.metrics['HealthCheckPassed_Topics'] || 0; - healthResData.total = data.metrics['HealthCheckTotal_Topics'] || 0; - healthResData.alive = data.metrics['Alive'] || 0; - setHealthData(healthResData); + setHealthData({ + state: data.metrics['HealthState'], + passed: data.metrics['HealthCheckPassed_Topics'] || 0, + total: data.metrics['HealthCheckTotal_Topics'] || 0, + }); }); }, []); return ; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/ZookeeperCard.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/ZookeeperCard.tsx new file mode 100644 index 00000000..8f6c65ed --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/ZookeeperCard.tsx @@ -0,0 +1,120 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import CardBar, { healthDataProps } from './index'; +import { Utils } from 'knowdesign'; +import api from '@src/api'; +import { HealthStateEnum } from '../HealthState'; + +interface ZookeeperState { + aliveFollowerCount: number; + aliveObserverCount: number; + aliveServerCount: number; + healthCheckPassed: number; + healthCheckTotal: number; + healthState: number; + leaderNode: string; + totalFollowerCount: number; + totalObserverCount: number; + totalServerCount: number; + watchCount: number; +} + +const getVal = (val: string | number | undefined | null) => { + return val === undefined || val === null || val === '' ? '-' : val; +}; + +const ZookeeperCard = () => { + const { clusterId } = useParams<{ + clusterId: string; + }>(); + const [loading, setLoading] = useState(false); + const [cardData, setCardData] = useState([]); + const [healthData, setHealthData] = useState({ + state: HealthStateEnum.UNKNOWN, + passed: 0, + total: 0, + }); + + const getHealthData = () => { + return Utils.post(api.getZookeeperMetricsInfo(Number(clusterId)), ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState']).then( + (data: any) => { + setHealthData({ + state: data?.metrics?.['HealthState'], + passed: data?.metrics?.['HealthCheckPassed'] || 0, + total: data?.metrics?.['HealthCheckTotal'] || 0, + }); + } + ); + }; + + const getCardInfo = () => { + return Utils.request(api.getZookeeperState(clusterId)).then((res: ZookeeperState) => { + const { + aliveFollowerCount, + aliveObserverCount, + aliveServerCount, + totalFollowerCount, + totalObserverCount, + totalServerCount, + watchCount, + leaderNode, + } = res || {}; + const cardMap = [ + { + title: 'Node Count', + value() { + return ( + + {aliveServerCount || '-'}/{totalServerCount || '-'} + + ); + }, + customStyle: { + // 自定义cardbar样式 + marginLeft: 0, + }, + }, + { + title: 'Watch Count', + value: getVal(watchCount), + }, + { + title: 'Leader', + value() { + return {leaderNode || '-'}; + }, + }, + { + title: 'Follower', + value() { + return ( + + {getVal(aliveFollowerCount)}/{getVal(totalFollowerCount)} + + ); + }, + }, + { + title: 'Observer', + value() { + return ( + + {getVal(aliveObserverCount)}/{getVal(totalObserverCount)} + + ); + }, + }, + ]; + setCardData(cardMap); + }); + }; + useEffect(() => { + setLoading(true); + Promise.all([getHealthData(), getCardInfo()]).finally(() => { + setLoading(false); + }); + }, [clusterId]); + return ; +}; + +export default ZookeeperCard; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less index ce7bc126..6474a929 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.less @@ -10,48 +10,15 @@ height: 88px; width: 100%; display: flex; - // justify-content: space-between; align-items: center; .card-bar-health { width: 240px; height: 70px; display: flex; align-items: center; - // justify-content: space-between; .card-bar-health-process { - height: 100%; - margin-right: 24px; - .dcloud-progress-inner { - border-radius: 50%; - } - .dcloud-progress-status-normal { - .dcloud-progress-inner { - background: rgba(85, 110, 230, 0.03); - } - .dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path { - stroke: rgb(85, 110, 230); - } - } - .dcloud-progress-status-success { - .dcloud-progress-inner { - background: rgba(0, 192, 162, 0.03); - } - .dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path { - stroke: rgb(0, 192, 162); - } - } - .dcloud-progress-status-exception { - .dcloud-progress-inner { - background: rgba(255, 112, 102, 0.03); - } - .dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path { - stroke: rgb(255, 112, 102); - } - } - .dcloud-progress-inner { - font-family: DIDIFD-Regular; - font-size: 40px !important; - } + padding-top: 30px; + margin-right: 20px; } .state { font-size: 13px; @@ -61,20 +28,6 @@ line-height: 20px; display: flex; align-items: center; - .health-status-image { - width: 15px; - height: 15px; - background-size: cover; - } - .health-status-image-success { - background-image: url('../../assets/health-status-success.png'); - } - .health-status-image-exception { - background-image: url('../../assets/health-status-exception.png'); - } - .health-status-image-normal { - background-image: url('../../assets/health-status-normal.png'); - } } .value-bar { display: flex; diff --git a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx index 0de8e813..51fc9057 100644 --- a/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx +++ b/km-console/packages/layout-clusters-fe/src/components/CardBar/index.tsx @@ -1,25 +1,24 @@ import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { Drawer, Select, Spin, Table } from 'knowdesign'; +import { Drawer, Spin, Table, Utils } from 'knowdesign'; import { IconFont } from '@knowdesign/icons'; -import { Utils, Progress } from 'knowdesign'; import './index.less'; import api from '@src/api'; import moment from 'moment'; import TagsWithHide from '../TagsWithHide/index'; -import { getHealthProcessColor } from '@src/pages/SingleClusterDetail/config'; +import HealthState, { getHealthStateDesc, getHealthStateEmoji, HealthStateEnum } from '../HealthState'; +import { getConfigItemDetailDesc } from '@src/pages/SingleClusterDetail/config'; export interface healthDataProps { - score: number; + state: HealthStateEnum; passed: number; total: number; - alive: number; } export interface CardBarProps { cardColumns?: any[]; healthData?: healthDataProps; showCardBg?: boolean; - scene: 'topic' | 'broker' | 'group'; + scene: 'topic' | 'broker' | 'group' | 'zookeeper'; record?: any; loading?: boolean; needProgress?: boolean; @@ -27,36 +26,27 @@ export interface CardBarProps { const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => { return typeof v === 'function' ? v(visibleType) : v; }; -const statusTxtEmojiMap = { - success: { - emoji: '👍', - txt: '优异', - }, - normal: { - emoji: '😊', - txt: '正常', - }, - exception: { - emoji: '👻', - txt: '异常', - }, -}; const sceneCodeMap = { - topic: { - code: 2, - fieldName: 'topicName', - alias: 'Topics', - }, broker: { code: 1, fieldName: 'brokerId', alias: 'Brokers', }, + topic: { + code: 2, + fieldName: 'topicName', + alias: 'Topics', + }, group: { code: 3, fieldName: 'groupName', alias: 'Consumers', }, + zookeeper: { + code: 4, + fieldName: 'zookeeperId', + alias: 'Zookeeper', + }, }; const CardColumnsItem: any = (cardItem: any) => { const { cardColumnsItemData, showCardBg } = cardItem; @@ -92,16 +82,7 @@ const CardBar = (props: CardBarProps) => { }>(); const { healthData, cardColumns, showCardBg = true, scene, record, loading, needProgress = true } = props; const [detailDrawerVisible, setDetailDrawerVisible] = useState(false); - const [progressStatus, setProgressStatus] = useState<'success' | 'exception' | 'normal'>('success'); const [healthCheckDetailList, setHealthCheckDetailList] = useState([]); - const [isAlive, setIsAlive] = useState(true); - - useEffect(() => { - if (healthData) { - setProgressStatus(!isAlive ? 'exception' : healthData.score >= 90 ? 'success' : 'normal'); - setIsAlive(healthData.alive === 1); - } - }, [healthData, isAlive]); useEffect(() => { const sceneObj = sceneCodeMap[scene]; @@ -120,23 +101,24 @@ const CardBar = (props: CardBarProps) => { const columns = [ { title: '检查项', - dataIndex: 'configDesc', - key: 'configDesc', - }, - { - title: '权重', - dataIndex: 'weightPercent', - key: 'weightPercent', - }, - { - title: '得分', - dataIndex: 'score', - key: 'score', + dataIndex: 'checkConfig', + render(config: any, record: any) { + let valueGroup = {}; + try { + valueGroup = JSON.parse(config.value); + } catch (e) { + // + } + return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-'; + }, }, + // { + // title: '得分', + // dataIndex: 'score', + // }, { title: '检查时间', dataIndex: 'updateTime', - key: 'updateTime', render: (value: number) => { return moment(value).format('YYYY-MM-DD HH:mm:ss'); }, @@ -144,8 +126,6 @@ const CardBar = (props: CardBarProps) => { { title: '检查结果', dataIndex: 'passed', - key: 'passed', - width: 280, render(value: boolean, record: any) { const icon = value ? : ; const txt = value ? '已通过' : '未通过'; @@ -168,40 +148,13 @@ const CardBar = (props: CardBarProps) => { {!loading && healthData && needProgress && (
- { - return !isAlive ? ( -
- Down -
- ) : ( -
= 100 ? '-4px' : '', - color: getHealthProcessColor(healthData.score, healthData.alive), - }} - > - {Math.round(percent)} -
- ); - }} - strokeWidth={3} - /> +
-
-  {sceneCodeMap[scene].alias}状态{statusTxtEmojiMap[progressStatus].txt} + {getHealthStateEmoji(healthData?.state)} +  {sceneCodeMap[scene].alias} + {getHealthStateDesc(healthData?.state)}
{`${healthData?.passed}/${healthData?.total}`}
diff --git a/km-console/packages/layout-clusters-fe/src/components/HealthState/index.less b/km-console/packages/layout-clusters-fe/src/components/HealthState/index.less new file mode 100644 index 00000000..64111ce0 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/HealthState/index.less @@ -0,0 +1,6 @@ +.health-state { + img { + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/km-console/packages/layout-clusters-fe/src/components/HealthState/index.tsx b/km-console/packages/layout-clusters-fe/src/components/HealthState/index.tsx new file mode 100644 index 00000000..3e34a850 --- /dev/null +++ b/km-console/packages/layout-clusters-fe/src/components/HealthState/index.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import GoodState from '@src/assets/health-good.png'; +import MediumState from '@src/assets/health-medium.png'; +import PoorState from '@src/assets/health-poor.png'; +import DownState from '@src/assets/health-down.png'; +import UnknownState from '@src/assets/health-unknown.png'; +import GoodStateEmoji from '@src/assets/health-good-emoji.png'; +import MediumStateEmoji from '@src/assets/health-medium-emoji.png'; +import PoorStateEmoji from '@src/assets/health-poor-emoji.png'; +import DownStateEmoji from '@src/assets/health-down-emoji.png'; +import './index.less'; + +export enum HealthStateEnum { + UNKNOWN = -1, + GOOD, + MEDIUM, + POOR, + DOWN, +} + +interface HealthStateProps { + state: HealthStateEnum; + width: string | number; + height: string | number; +} + +const HEALTH_STATE_MAP = { + [HealthStateEnum.GOOD]: GoodState, + [HealthStateEnum.MEDIUM]: MediumState, + [HealthStateEnum.POOR]: PoorState, + [HealthStateEnum.DOWN]: DownState, + [HealthStateEnum.UNKNOWN]: UnknownState, +}; + +const HEALTH_STATE_EMOJI_MAP = { + [HealthStateEnum.GOOD]: GoodStateEmoji, + [HealthStateEnum.MEDIUM]: MediumStateEmoji, + [HealthStateEnum.POOR]: PoorStateEmoji, + [HealthStateEnum.DOWN]: DownStateEmoji, + [HealthStateEnum.UNKNOWN]: DownStateEmoji, +}; + +const HEALTH_STATE_DESC_MAP = { + [HealthStateEnum.GOOD]: '状态优异', + [HealthStateEnum.MEDIUM]: '状态良好', + [HealthStateEnum.POOR]: '状态较差', + [HealthStateEnum.DOWN]: '状态异常', + [HealthStateEnum.UNKNOWN]: '状态异常', +}; + +export const getHealthStateEmoji = (state: HealthStateEnum, width = 16, height = 16) => { + return ( + + ); +}; + +export const getHealthStateDesc = (state: HealthStateEnum) => { + return HEALTH_STATE_DESC_MAP[state] || HEALTH_STATE_DESC_MAP[HealthStateEnum.UNKNOWN]; +}; + +const HealthState = (props: HealthStateProps) => { + const { state, width, height } = props; + + return ( +
+ +
+ ); +}; + +export default HealthState; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx index 6cebe36a..25e5cdfe 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/HomePage.tsx @@ -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(); + const [sliderInfo, setSliderInfo] = React.useState<{ + value: [number, number]; + desc: string; + }>({ + value: [0, 5], + desc: '', + }); // TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题 const [searchParams, setSearchParams] = React.useState({ 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 = () => {
-

健康分

-
- -
+

健康状态

+ +
+ { + 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); + }} + /> +
+
@@ -269,7 +330,7 @@ const MultiClusterPage = () => {