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 e7446095..908c2f57 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,8 +1,8 @@ import { Button, Divider, Drawer, Form, Input, InputNumber, message, Radio, Select, Spin, Space, Utils } from 'knowdesign'; import * as React from 'react'; import { useIntl } from 'react-intl'; -import api from '../../api'; -import { regClusterName, regUsername } from '../../constants/reg'; +import api from '@src/api'; +import { regClusterName, regUsername } from '@src/constants/reg'; import { bootstrapServersErrCodes, jmxErrCodes, zkErrCodes } from './config'; import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem'; @@ -21,40 +21,28 @@ word=\\"xxxxxx\\";" `; const AccessClusters = (props: any): JSX.Element => { + const { afterSubmitSuccess, clusterInfo, visible } = props; + const intl = useIntl(); const [form] = Form.useForm(); - - const { afterSubmitSuccess, infoLoading, clusterInfo, visible } = props; const [loading, setLoading] = React.useState(false); - const [security, setSecurity] = React.useState(clusterInfo?.security || 'None'); + const [curClusterInfo, setCurClusterInfo] = React.useState({}); + const [security, setSecurity] = React.useState(curClusterInfo?.security || 'None'); const [extra, setExtra] = React.useState({ versionExtra: '', zooKeeperExtra: '', bootstrapExtra: '', jmxExtra: '', }); - const [isLowVersion, setIsLowVersion] = React.useState(false); - const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState(false); + const [isLowVersion, setIsLowVersion] = React.useState(false); + const [zookeeperErrorStatus, setZookeeperErrorStatus] = React.useState(false); const lastFormItemValue = React.useRef({ - bootstrap: clusterInfo?.bootstrapServers || '', - zookeeper: clusterInfo?.zookeeper || '', - clientProperties: clusterInfo?.clientProperties || {}, + bootstrap: curClusterInfo?.bootstrapServers || '', + zookeeper: curClusterInfo?.zookeeper || '', + clientProperties: curClusterInfo?.clientProperties || {}, }); - React.useEffect(() => { - const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion); - lastFormItemValue.current.bootstrap = clusterInfo?.bootstrapServers || ''; - lastFormItemValue.current.zookeeper = clusterInfo?.zookeeper || ''; - lastFormItemValue.current.clientProperties = clusterInfo?.clientProperties || {}; - setIsLowVersion(showLowVersion); - setExtra({ - ...extra, - versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '', - }); - form.setFieldsValue({ ...clusterInfo }); - }, [clusterInfo]); - const onHandleValuesChange = (value: any, allValues: any) => { Object.keys(value).forEach((key) => { switch (key) { @@ -128,10 +116,10 @@ const AccessClusters = (props: any): JSX.Element => { zookeeper: res.zookeeper || '', }; setLoading(true); - if (!isNaN(clusterInfo?.id)) { + if (!isNaN(curClusterInfo?.id)) { Utils.put(api.phyCluster, { ...params, - id: clusterInfo?.id, + id: curClusterInfo?.id, }) .then(() => { message.success('编辑成功'); @@ -219,7 +207,11 @@ const AccessClusters = (props: any): JSX.Element => { }); // 如果kafkaVersion小于最低版本则提示 - const showLowVersion = !(clusterInfo?.zookeeper || !clusterInfo?.kafkaVersion || clusterInfo?.kafkaVersion >= lowKafkaVersion); + const showLowVersion = !( + curClusterInfo?.zookeeper || + !curClusterInfo?.kafkaVersion || + curClusterInfo?.kafkaVersion >= lowKafkaVersion + ); setIsLowVersion(showLowVersion); setExtra({ ...extraMsg, @@ -232,6 +224,55 @@ const AccessClusters = (props: any): JSX.Element => { }); }; + React.useEffect(() => { + const showLowVersion = !(curClusterInfo?.zookeeper || !curClusterInfo?.kafkaVersion || curClusterInfo?.kafkaVersion >= lowKafkaVersion); + lastFormItemValue.current = { + bootstrap: curClusterInfo?.bootstrapServers || '', + zookeeper: curClusterInfo?.zookeeper || '', + clientProperties: curClusterInfo?.clientProperties || {}, + }; + setIsLowVersion(showLowVersion); + setExtra({ + ...extra, + versionExtra: showLowVersion ? intl.formatMessage({ id: 'access.cluster.low.version.tip' }) : '', + }); + form.setFieldsValue({ ...curClusterInfo }); + }, [curClusterInfo]); + + React.useEffect(() => { + if (visible) { + if (clusterInfo?.id) { + setLoading(true); + Utils.request(api.getPhyClusterBasic(clusterInfo.id)) + .then((res: any) => { + let jmxProperties = null; + try { + jmxProperties = JSON.parse(res?.jmxProperties); + } catch (err) { + console.error(err); + } + + // 转化值对应成表单值 + if (jmxProperties?.openSSL) { + jmxProperties.security = 'Password'; + } + + if (jmxProperties) { + res = Object.assign({}, res || {}, jmxProperties); + } + setCurClusterInfo(res); + setLoading(false); + }) + .catch((err) => { + setCurClusterInfo(clusterInfo); + setLoading(false); + }); + } else { + setCurClusterInfo(clusterInfo); + } + } + }, [visible, clusterInfo]); + return ( <> { placement="right" width={480} > - -
+ + { if (!value) { return Promise.reject('集群名称不能为空'); } - - if (value === clusterInfo?.name) { + if (value === curClusterInfo?.name) { return Promise.resolve(); } - if (value?.length > 128) { return Promise.reject('集群名称长度限制在1~128字符'); } @@ -307,13 +338,7 @@ const AccessClusters = (props: any): JSX.Element => { {extra.bootstrapExtra} - ) : ( - {extra.bootstrapExtra} - ) - } + extra={{extra.bootstrapExtra}} validateTrigger={'onBlur'} rules={[ { @@ -349,13 +374,7 @@ const AccessClusters = (props: any): JSX.Element => { {extra.zooKeeperExtra} - ) : ( - {extra.zooKeeperExtra} - ) - } + extra={{extra.zooKeeperExtra}} validateStatus={zookeeperErrorStatus ? 'error' : 'success'} validateTrigger={'onBlur'} rules={[ @@ -458,7 +477,7 @@ const AccessClusters = (props: any): JSX.Element => { style={{ width: '58%' }} rules={[ { - required: security === 'Password' || clusterInfo?.security === 'Password', + required: security === 'Password' || curClusterInfo?.security === 'Password', validator: async (rule: any, value: string) => { if (!value) { return Promise.reject('用户名不能为空'); @@ -483,7 +502,7 @@ const AccessClusters = (props: any): JSX.Element => { style={{ width: '38%', marginRight: 0 }} rules={[ { - required: security === 'Password' || clusterInfo?.security === 'Password', + required: security === 'Password' || curClusterInfo?.security === 'Password', validator: async (rule: any, value: string) => { if (!value) { return Promise.reject('密码不能为空'); diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/CustomCheckGroup.tsx b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/CustomCheckGroup.tsx index db8ed44e..124665f9 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/CustomCheckGroup.tsx +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/CustomCheckGroup.tsx @@ -1,102 +1,108 @@ import { DoubleRightOutlined } from '@ant-design/icons'; import { Checkbox } from 'knowdesign'; +import { CheckboxValueType } from 'knowdesign/es/basic/checkbox/Group'; import { debounce } from 'lodash'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; const CheckboxGroup = Checkbox.Group; -interface IVersion { - firstLine: string[]; - leftVersions: string[]; -} - const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup: any }) => { - const { kafkaVersions, onChangeCheckGroup } = props; - const [checkedKafkaVersion, setCheckedKafkaVersion] = React.useState({ - firstLine: [], - leftVersions: [], - }); - const [allVersion, setAllVersion] = React.useState({ - firstLine: [], - leftVersions: [], - }); - + const { kafkaVersions: newVersions, onChangeCheckGroup } = props; + const [versions, setVersions] = React.useState([]); + const [versionsState, setVersionsState] = React.useState<{ + [key: string]: boolean; + }>({}); const [indeterminate, setIndeterminate] = React.useState(false); const [checkAll, setCheckAll] = React.useState(true); - const [moreGroupWidth, setMoreGroupWidth] = React.useState(400); + const [groupInfo, setGroupInfo] = useState({ + width: 400, + num: 0, + }); const [showMore, setShowMore] = React.useState(false); - useEffect(() => { - document.addEventListener('click', handleDocumentClick); - return () => { - document.removeEventListener('click', handleDocumentClick); - }; - }, []); - const handleDocumentClick = (e: Event) => { setShowMore(false); }; - const setCheckAllStauts = (list: string[], otherList: string[]) => { - onChangeCheckGroup([...list, ...otherList]); - setIndeterminate(!!list.length && list.length + otherList.length < kafkaVersions.length); - setCheckAll(list.length + otherList.length === kafkaVersions.length); - }; - - const getTwoPanelVersion = () => { + const updateGroupInfo = () => { const width = (document.getElementsByClassName('custom-check-group')[0] as any)?.offsetWidth; const checkgroupWidth = width - 100 - 86; const num = (checkgroupWidth / 108) | 0; - const firstLine = Array.from(kafkaVersions).splice(0, num); - setMoreGroupWidth(num * 108 + 88 + 66); - const leftVersions = Array.from(kafkaVersions).splice(num); - return { firstLine, leftVersions }; + setGroupInfo({ + width: num * 108 + 88 + 66, + num, + }); }; - const onFirstVersionChange = (list: []) => { - setCheckedKafkaVersion({ - ...checkedKafkaVersion, - firstLine: list, - }); - - setCheckAllStauts(list, checkedKafkaVersion.leftVersions); + const getCheckedList = ( + versionState: { + [key: string]: boolean; + }, + filterFunc: (item: [string, boolean], i: number) => boolean + ) => { + return Object.entries(versionState) + .filter(filterFunc) + .map(([key]) => key); }; - const onLeftVersionChange = (list: []) => { - setCheckedKafkaVersion({ - ...checkedKafkaVersion, - leftVersions: list, + const onVersionsChange = (isFirstLine: boolean, list: CheckboxValueType[]) => { + const newVersionsState = { ...versionsState }; + Object.keys(newVersionsState).forEach((key, i) => { + if (isFirstLine && i < groupInfo.num) { + newVersionsState[key] = list.includes(key); + } else if (!isFirstLine && i >= groupInfo.num) { + newVersionsState[key] = list.includes(key); + } }); - setCheckAllStauts(list, checkedKafkaVersion.firstLine); + const checkedLen = Object.values(newVersionsState).filter((v) => v).length; + + setVersionsState(newVersionsState); + setIndeterminate(checkedLen && checkedLen < newVersions.length); + setCheckAll(checkedLen === newVersions.length); + onChangeCheckGroup(getCheckedList(newVersionsState, ([, state]) => state)); }; const onCheckAllChange = (e: any) => { - const versions = getTwoPanelVersion(); - - setCheckedKafkaVersion( - e.target.checked - ? versions - : { - firstLine: [], - leftVersions: [], - } - ); - onChangeCheckGroup(e.target.checked ? [...versions.firstLine, ...versions.leftVersions] : []); + const checked = e.target.checked; + const newVersionsState = { ...versionsState }; + Object.keys(newVersionsState).forEach((key) => (newVersionsState[key] = checked)); + setVersionsState(newVersionsState); setIndeterminate(false); - setCheckAll(e.target.checked); + setCheckAll(checked); + onChangeCheckGroup(e.target.checked ? versions : []); }; - React.useEffect(() => { - const handleVersionLine = () => { - const versions = getTwoPanelVersion(); - setAllVersion(versions); - setCheckedKafkaVersion(versions); - }; - handleVersionLine(); + useEffect(() => { + const newVersionsState = { ...versionsState }; + Object.keys(newVersionsState).forEach((key) => { + if (!newVersions.includes(key)) { + delete newVersionsState[key]; + } + }); + newVersions.forEach((version) => { + if (!Object.keys(newVersionsState).includes(version)) { + newVersionsState[version] = true; + } + }); + const checkedLen = Object.values(newVersionsState).filter((v) => v).length; - window.addEventListener('resize', handleVersionLine); //监听窗口大小改变 - return () => window.removeEventListener('resize', debounce(handleVersionLine, 500)); + setVersions([...newVersions]); + setVersionsState(newVersionsState); + setIndeterminate(checkedLen && checkedLen < newVersions.length); + setCheckAll(checkedLen === newVersions.length); + onChangeCheckGroup(getCheckedList(newVersionsState, ([, state]) => state)); + }, [newVersions]); + + useEffect(() => { + updateGroupInfo(); + const listen = debounce(updateGroupInfo, 500); + window.addEventListener('resize', listen); //监听窗口大小改变 + document.addEventListener('click', handleDocumentClick); + return () => { + window.removeEventListener('resize', listen); + document.removeEventListener('click', handleDocumentClick); + }; }, []); return ( @@ -107,17 +113,21 @@ const CustomCheckGroup = (props: { kafkaVersions: string[]; onChangeCheckGroup: 全选 - + i < groupInfo.num && state)} + onChange={(list) => onVersionsChange(true, list)} + /> {showMore ? ( i >= groupInfo.num && state)} + onChange={(list) => onVersionsChange(false, list)} /> ) : null} - {allVersion.leftVersions.length ? ( + {versions.length > groupInfo.num ? (
setShowMore(!showMore)}> {!showMore ? '展开更多' : '收起更多'} 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 4839f54c..473bb2dc 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,11 +1,10 @@ -import React, { useEffect, useMemo, useRef, useState, useReducer } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Slider, Input, Select, Checkbox, Button, Utils, Spin, IconFont, AppContainer } from 'knowdesign'; -import API from '../../api'; +import API from '@src/api'; import TourGuide, { MultiPageSteps } from '@src/components/TourGuide'; import './index.less'; -import { healthSorceList, linesMetric, pointsMetric, sortFieldList, sortTypes, statusFilters } from './config'; -import { oneDayMillims } from '../../constants/common'; -import ListScroll from './List'; +import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config'; +import ClusterList from './List'; import AccessClusters from './AccessCluster'; import CustomCheckGroup from './CustomCheckGroup'; import { ClustersPermissionMap } from '../CommonConfig'; @@ -13,98 +12,85 @@ import { ClustersPermissionMap } from '../CommonConfig'; const CheckboxGroup = Checkbox.Group; const { Option } = Select; +interface ClustersState { + liveCount: number; + downCount: number; + total: number; +} + +export interface SearchParams { + healthScoreRange?: [number, number]; + checkedKafkaVersions?: string[]; + sortInfo?: { + sortField: string; + sortType: string; + }; + keywords?: string; + clusterStatus?: number[]; + isReloadAll?: boolean; +} + +// 未接入集群默认页 +const DefaultPage = (props: { setVisible: (visible: boolean) => void }) => { + return ( +
+
Kafka 多集群管理
+
+
+
+
+
+
+ +
+
+ ); +}; + +// 加载状态 +const LoadingState = () => { + return ( +
+ +
+ ); +}; + const MultiClusterPage = () => { - const [run, setRun] = useState(false); const [global] = AppContainer.useGlobalValue(); - const [statusList, setStatusList] = React.useState([1, 0]); + const [pageLoading, setPageLoading] = useState(true); + const [accessClusterVisible, setAccessClusterVisible] = React.useState(false); + const [curClusterInfo, setCurClusterInfo] = useState({}); const [kafkaVersions, setKafkaVersions] = React.useState([]); const [existKafkaVersion, setExistKafkaVersion] = React.useState([]); - const [visible, setVisible] = React.useState(false); - const [list, setList] = useState<[]>([]); - const [healthScoreRange, setHealthScoreRange] = React.useState([0, 100]); - const [checkedKafkaVersions, setCheckedKafkaVersions] = React.useState([]); - const [sortInfo, setSortInfo] = React.useState({ - sortField: 'HealthScore', - sortType: 'asc', - }); - const [clusterLoading, setClusterLoading] = useState(true); - const [pageLoading, setPageLoading] = useState(true); - const [isReload, setIsReload] = useState(false); - const [versionLoading, setVersionLoading] = useState(true); - const [searchKeywords, setSearchKeywords] = useState(''); - const [stateInfo, setStateInfo] = React.useState({ + const [stateInfo, setStateInfo] = React.useState({ downCount: 0, liveCount: 0, total: 0, }); - const [pagination, setPagination] = useState({ - pageNo: 1, - pageSize: 10, - total: 0, + // TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题 + const [searchParams, setSearchParams] = React.useState({ + keywords: '', + checkedKafkaVersions: [], + healthScoreRange: [0, 100], + sortInfo: { + sortField: 'HealthScore', + sortType: 'asc', + }, + clusterStatus: [0, 1], + // 是否拉取当前所有数据 + isReloadAll: false, }); const searchKeyword = useRef(''); + const isReload = useRef(false); - const getPhyClustersDashbord = (pageNo: number, pageSize: number) => { - const endTime = new Date().getTime(); - const startTime = endTime - oneDayMillims; - const params = { - metricLines: { - endTime, - metricsNames: linesMetric, - startTime, - }, - latestMetricNames: pointsMetric, - pageNo: pageNo || 1, - pageSize: pageSize || 10, - preciseFilterDTOList: [ - { - fieldName: 'kafkaVersion', - fieldValueList: checkedKafkaVersions as (string | number)[], - }, - ], - rangeFilterDTOList: [ - { - fieldMaxValue: healthScoreRange[1], - fieldMinValue: healthScoreRange[0], - fieldName: 'HealthScore', - }, - ], - searchKeywords, - ...sortInfo, - }; - - if (statusList.length === 1) { - params.preciseFilterDTOList.push({ - fieldName: 'Alive', - fieldValueList: statusList, - }); - } - return Utils.post(API.phyClustersDashbord, params); - }; - - const getSupportKafkaVersion = () => { - Utils.request(API.supportKafkaVersion).then((res) => { - setKafkaVersions(Object.keys(res || {})); - }); - }; - - const getExistKafkaVersion = () => { - setVersionLoading(true); - Utils.request(API.getClustersVersion) - .then((versions: string[]) => { - if (!Array.isArray(versions)) { - versions = []; - } - setExistKafkaVersion(versions.sort().reverse() || []); - setVersionLoading(false); - setCheckedKafkaVersions(versions || []); - }) - .catch((err) => { - setVersionLoading(false); - }); - }; - + // 获取集群状态 const getPhyClusterState = () => { Utils.request(API.phyClusterState) .then((res: any) => { @@ -115,213 +101,224 @@ const MultiClusterPage = () => { }); }; + // 获取 kafka 全部版本 + const getSupportKafkaVersion = () => { + Utils.request(API.supportKafkaVersion).then((res) => { + setKafkaVersions(Object.keys(res || {})); + }); + }; + + const updateSearchParams = (params: SearchParams) => { + setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params })); + }; + + const searchParamsChangeFunc = { + // 健康分改变 + onSilderChange: (value: [number, number]) => + updateSearchParams({ + healthScoreRange: value, + }), + // 排序信息改变 + onSortInfoChange: (type: string, value: string) => + updateSearchParams({ + sortInfo: { + ...searchParams.sortInfo, + [type]: value, + }, + }), + // Live / Down 筛选 + onClusterStatusChange: (list: number[]) => + updateSearchParams({ + clusterStatus: list, + }), + // 集群名称搜索项改变 + onInputChange: () => + updateSearchParams({ + keywords: searchKeyword.current, + }), + // 集群版本筛选 + onChangeCheckGroup: (list: string[]) => { + updateSearchParams({ + checkedKafkaVersions: list, + isReloadAll: isReload.current, + }); + isReload.current = false; + }, + }; + + // 获取当前接入集群的 kafka 版本 + const getExistKafkaVersion = (isReloadAll = false) => { + isReload.current = isReloadAll; + Utils.request(API.getClustersVersion).then((versions: string[]) => { + if (!Array.isArray(versions)) { + versions = []; + } + setExistKafkaVersion(versions.sort().reverse() || []); + }); + }; + + // 接入/编辑集群 + const showAccessCluster = (clusterInfo: any = {}) => { + setCurClusterInfo(clusterInfo); + setAccessClusterVisible(true); + }; + // 接入/编辑集群回调 + const afterAccessCluster = () => { + getPhyClusterState(); + getExistKafkaVersion(true); + }; + useEffect(() => { getPhyClusterState(); getSupportKafkaVersion(); getExistKafkaVersion(); }, []); - useEffect(() => { - if (!pageLoading && stateInfo.total) { - setRun(true); - } - }, [pageLoading, stateInfo]); - - useEffect(() => { - if (versionLoading) return; - setClusterLoading(true); - getPhyClustersDashbord(pagination.pageNo, pagination.pageSize) - .then((res: any) => { - setPagination(res.pagination); - setList(res?.bizData || []); - return res; - }) - .finally(() => { - setClusterLoading(false); - }); - }, [sortInfo, checkedKafkaVersions, healthScoreRange, statusList, searchKeywords, isReload]); - - const onSilderChange = (value: number[]) => { - setHealthScoreRange(value); - }; - - const onSelectChange = (type: string, value: string) => { - setSortInfo({ - ...sortInfo, - [type]: value, - }); - }; - - const onStatusChange = (list: []) => { - setStatusList(list); - }; - - const onInputChange = (e: any) => { - const { value } = e.target; - setSearchKeywords(value.trim()); - }; - - const onChangeCheckGroup = (list: []) => { - setCheckedKafkaVersions(list); - }; - - const afterSubmitSuccessAccessClusters = () => { - getPhyClusterState(); - setIsReload(!isReload); - }; - - const renderEmpty = () => { - return ( -
-
Kafka 多集群管理
-
-
-
-
-
-
- -
-
- ); - }; - - const renderLoading = () => { - return ( -
- -
- ); - }; - - const renderContent = () => { - return ( -
-
-
-
-
-
-
-
- Clusters 总数 -
-
{stateInfo.total}
-
-
-
- live - - {stateInfo.liveCount} - -
-
-
-
- down - - {stateInfo.downCount} - -
-
-
-
-
-
-
- (searchKeyword.current = e.target.value)} - allowClear - bordered={false} - placeholder="请输入ClusterName进行搜索" - suffix={ setSearchKeywords(searchKeyword.current)} />} - /> -
- {global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_ADD) ? ( - <> -
- - - ) : ( - <> - )} -
- -
-
-

版本分布

-
- {existKafkaVersion.length ? ( - - ) : null} -
-
-
-

健康分

-
- -
-
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
- {renderList} -
-
- ); - }; - - const renderList = useMemo(() => { - return ; - }, [list, pagination]); - return ( <> - - {pageLoading ? renderLoading() : stateInfo.total ? renderContent() : renderEmpty()} + {pageLoading ? ( + + ) : !stateInfo?.total ? ( + + ) : ( + <> +
+
+
+
+
+
+
+
+ Clusters 总数 +
+
{stateInfo.total}
+
+
+
+ live + + {stateInfo.liveCount} + +
+
+
+
+ down + + {stateInfo.downCount} + +
+
+
+
+
+
+
+ (searchKeyword.current = e.target.value)} + allowClear + bordered={false} + placeholder="请输入ClusterName进行搜索" + suffix={} + /> +
+ {global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_ADD) ? ( + <> +
+ + + ) : ( + <> + )} +
+ +
+
+

版本分布

+
+ {existKafkaVersion.length ? ( + + ) : null} +
+
+
+

健康分

+
+ +
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+ +
+
+ {/* 引导页 */} + + + )} + ); 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 d5e7edfe..a65ac3fd 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,38 +1,51 @@ import { AppContainer, Divider, Form, IconFont, Input, List, message, Modal, Progress, Spin, Tooltip, Utils } from 'knowdesign'; import moment from 'moment'; -import React, { useEffect, useMemo, useState, useReducer } from 'react'; +import API from '@src/api'; +import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import InfiniteScroll from 'react-infinite-scroll-component'; import { Link, useHistory } from 'react-router-dom'; -import { timeFormat } from '../../constants/common'; -import { IMetricPoint, linesMetric } from './config'; +import { timeFormat, oneDayMillims } from '@src/constants/common'; +import { IMetricPoint, linesMetric, pointsMetric } from './config'; import { useIntl } from 'react-intl'; -import api, { MetricType } from '../../api'; +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 SmallChart from '@src/components/SmallChart'; +import { SearchParams } from './HomePage'; -const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getPhyClusterState: any }) => { - const history = useHistory(); - const [global] = AppContainer.useGlobalValue(); - const [form] = Form.useForm(); - const [list, setList] = useState<[]>(props.list || []); - const [loading, setLoading] = useState(false); - const [visible, setVisible] = useState(false); - const [clusterInfo, setClusterInfo] = useState({} as any); - const [pagination, setPagination] = useState( - props.pagination || { - pageNo: 1, - pageSize: 10, - total: 0, - } - ); +const DEFAULT_PAGE_SIZE = 10; + +const DeleteCluster = React.forwardRef((_, ref) => { const intl = useIntl(); + const [form] = Form.useForm(); + const [visible, setVisible] = useState(false); + const [clusterInfo, setClusterInfo] = useState({}); + const callback = useRef(() => { + return; + }); - useEffect(() => { - setList(props.list || []); - setPagination(props.pagination || {}); - }, [props.list, props.pagination]); + const onFinish = () => { + form.validateFields().then(() => { + Utils.delete(api.phyCluster, { + params: { + clusterPhyId: clusterInfo.id, + }, + }).then(() => { + message.success('删除成功'); + callback.current(); + setVisible(false); + }); + }); + }; + + useImperativeHandle(ref, () => ({ + onOpen: (clusterInfo: any, cbk: () => void) => { + setClusterInfo(clusterInfo); + callback.current = cbk; + setVisible(true); + }, + })); useEffect(() => { if (visible) { @@ -40,19 +53,164 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP } }, [visible]); + return ( + setVisible(false)} + okButtonProps={{ + style: { + width: 56, + }, + danger: true, + size: 'small', + }} + cancelButtonProps={{ + style: { + width: 56, + }, + size: 'small', + }} + > +
+ + + {intl.formatMessage({ + id: 'delete.cluster.confirm.tip', + })} + +
+ + + {clusterInfo.name} + + { + value = value || ''; + if (!value.trim() || value.trim() !== clusterInfo.name) + return Promise.reject( + intl.formatMessage({ + id: 'delete.cluster.confirm.cluster', + }) + ); + return Promise.resolve(); + }, + }, + ]} + > + + + +
+ ); +}); + +const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any; getPhyClusterState: any; getExistKafkaVersion: any }) => { + const { searchParams, showAccessCluster, getPhyClusterState, getExistKafkaVersion } = props; + const history = useHistory(); + const [global] = AppContainer.useGlobalValue(); + const [isReload, setIsReload] = useState(false); + const [list, setList] = useState<[]>([]); + const [clusterLoading, setClusterLoading] = useState(true); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [pagination, setPagination] = useState({ + pageNo: 1, + pageSize: DEFAULT_PAGE_SIZE, + total: 0, + }); + const deleteModalRef = useRef(null); + + const getClusterList = (pageNo: number, pageSize: number) => { + const endTime = new Date().getTime(); + const startTime = endTime - oneDayMillims; + const params = { + metricLines: { + endTime, + metricsNames: linesMetric, + startTime, + }, + latestMetricNames: pointsMetric, + pageNo: pageNo, + pageSize: pageSize, + preciseFilterDTOList: [ + { + fieldName: 'kafkaVersion', + fieldValueList: searchParams.checkedKafkaVersions as (string | number)[], + }, + ], + rangeFilterDTOList: [ + { + fieldMaxValue: searchParams.healthScoreRange[1], + fieldMinValue: searchParams.healthScoreRange[0], + fieldName: 'HealthScore', + }, + ], + searchKeywords: searchParams.keywords, + ...searchParams.sortInfo, + }; + + if (searchParams.clusterStatus.length === 1) { + params.preciseFilterDTOList.push({ + fieldName: 'Alive', + fieldValueList: searchParams.clusterStatus, + }); + } + return Utils.post(API.phyClustersDashbord, params); + }; + + // 重置集群列表 + const reloadClusterList = (pageSize = DEFAULT_PAGE_SIZE) => { + setClusterLoading(true); + getClusterList(1, pageSize) + .then((res: any) => { + setList(res?.bizData || []); + setPagination(res.pagination); + }) + .finally(() => setClusterLoading(false)); + }; + + // 加载更多列表 const loadMoreData = async () => { - if (loading) { + if (isLoadingMore) { return; } - setLoading(true); + setIsLoadingMore(true); - const res = await props.loadMoreData(pagination.pageNo + 1, pagination.pageSize); + const res: any = await getClusterList(pagination.pageNo + 1, pagination.pageSize); const _data = list.concat(res.bizData || []) as any; setList(_data); setPagination(res.pagination); - setLoading(false); + setIsLoadingMore(false); }; + // 重载列表 + useEffect( + () => (searchParams.isReloadAll ? reloadClusterList(pagination.pageNo * pagination.pageSize) : reloadClusterList()), + [searchParams] + ); + const RenderItem = (itemData: any) => { itemData = itemData || {}; const metrics = linesMetric; @@ -160,7 +318,7 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP title={ 尚未开启 {name} 均衡策略, - 前往开启 + 前往开启 } > @@ -225,11 +383,34 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP
- {global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) ? ( + {global.hasPermission ? (
-
onClickDeleteBtn(event, itemData)}> - -
+ {global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_INFO) && ( +
{ + e.stopPropagation(); + showAccessCluster(itemData); + }} + > + +
+ )} + {global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) && ( +
{ + e.stopPropagation(); + deleteModalRef.current.onOpen(itemData, () => { + getPhyClusterState(); + getExistKafkaVersion(true); + reloadClusterList(pagination.pageNo * pagination.pageSize); + }); + }} + > + +
+ )}
) : ( <> @@ -239,45 +420,21 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP ); }; - const onFinish = () => { - form.validateFields().then((formData) => { - Utils.delete(api.phyCluster, { - params: { - clusterPhyId: clusterInfo.id, - }, - }).then((res) => { - message.success('删除成功'); - setVisible(false); - props?.getPhyClusterState(); - const fliterList: any = list.filter((item: any) => { - return item?.id !== clusterInfo.id; - }); - setList(fliterList || []); - }); - }); - }; - - const onClickDeleteBtn = (event: any, clusterInfo: any) => { - event.stopPropagation(); - setClusterInfo(clusterInfo); - setVisible(true); - }; - return ( - <> + {useMemo( () => ( } + loader={} endMessage={ !pagination.total ? ( '' ) : ( - 加载完成 共{pagination.total}条 + 加载完成 共 {pagination.total} 条 ) } @@ -293,81 +450,11 @@ const ListScroll = (props: { loadMoreData: any; list: any; pagination: any; getP /> ), - [list, pagination, loading] + [list, pagination, isLoadingMore] )} - setVisible(false)} - okButtonProps={{ - style: { - width: 56, - }, - danger: true, - size: 'small', - }} - cancelButtonProps={{ - style: { - width: 56, - }, - size: 'small', - }} - > -
- - - {intl.formatMessage({ - id: 'delete.cluster.confirm.tip', - })} - -
-
- - {clusterInfo.name} - - { - value = value || ''; - if (!value.trim() || value.trim() !== clusterInfo.name) - return Promise.reject( - intl.formatMessage({ - id: 'delete.cluster.confirm.cluster', - }) - ); - return Promise.resolve(); - }, - }, - ]} - > - - -
-
- + +
); }; - -export default ListScroll; +export default ClusterList; diff --git a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less index 1d79b7db..3108c4c0 100644 --- a/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less +++ b/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/index.less @@ -364,8 +364,12 @@ .multi-cluster-list-item-btn { opacity: 1; .icon { + width: 24px; + background: rgba(33, 37, 41, 0.04); + border-radius: 12px; color: #74788d; font-size: 14px; + margin-left: 10px; } .icon:hover { @@ -375,16 +379,14 @@ } .multi-cluster-list-item-btn { + display: flex; opacity: 0; position: absolute; right: 20px; top: 8px; z-index: 10; text-align: right; - width: 24px; height: 24px; - background: rgba(33, 37, 41, 0.04); - border-radius: 14px; text-align: center; line-height: 24px; }