Files
KnowStreaming/km-console/packages/layout-clusters-fe/src/pages/MutliClusterPage/List.tsx
2022-09-01 19:50:36 +08:00

374 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 InfiniteScroll from 'react-infinite-scroll-component';
import { Link, useHistory } from 'react-router-dom';
import { timeFormat } from '../../constants/common';
import { IMetricPoint, linesMetric } from './config';
import { useIntl } from 'react-intl';
import api, { MetricType } from '../../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';
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 intl = useIntl();
useEffect(() => {
setList(props.list || []);
setPagination(props.pagination || {});
}, [props.list, props.pagination]);
useEffect(() => {
if (visible) {
form.resetFields();
}
}, [visible]);
const loadMoreData = async () => {
if (loading) {
return;
}
setLoading(true);
const res = await props.loadMoreData(pagination.pageNo + 1, pagination.pageSize);
const _data = list.concat(res.bizData || []) as any;
setList(_data);
setPagination(res.pagination);
setLoading(false);
};
const RenderItem = (itemData: any) => {
itemData = itemData || {};
const metrics = linesMetric;
const metricPoints = [] as IMetricPoint[];
metrics.forEach((item) => {
const line = {
metricName: item,
value: itemData.latestMetrics?.metrics?.[item] || 0,
unit: (global.getMetricDefine && global.getMetricDefine(MetricType.Cluster, item).unit) || '',
metricLines: {
name: item,
data: itemData.metricLines
.find((metric: any) => metric.metricName === item)
?.metricPoints.map((point: IMetricPoint) => [point.timeStamp, point.value]),
},
} as IMetricPoint;
// 如果单位是 字节 ,进行单位换算
if (line.unit.toLowerCase().includes('byte')) {
const [unit, size] = getUnit(line.value);
line.value = Number((line.value / size).toFixed(2));
line.unit = line.unit.toLowerCase().replace('byte', unit);
}
// Messages 指标值特殊处理
if (line.metricName === 'LeaderMessages') {
const [unit, size] = getDataNumberUnit(line.value);
line.value = Number((line.value / size).toFixed(2));
line.unit = unit + line.unit;
}
metricPoints.push(line);
});
const {
Brokers: brokers,
Zookeepers: zks,
HealthCheckPassed: healthCheckPassed,
HealthCheckTotal: healthCheckTotal,
HealthScore: healthScore,
ZookeepersAvailable: zookeepersAvailable,
LoadReBalanceCpu: loadReBalanceCpu,
LoadReBalanceDisk: loadReBalanceDisk,
LoadReBalanceEnable: loadReBalanceEnable,
LoadReBalanceNwIn: loadReBalanceNwIn,
LoadReBalanceNwOut: loadReBalanceNwOut,
} = itemData.latestMetrics?.metrics || {};
return (
<List.Item
onClick={() => {
history.push(`/cluster/${itemData.id}/cluster`);
}}
>
<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>
</div>
<div className="multi-cluster-list-item-right">
<div className="multi-cluster-list-item-base">
<div className="multi-cluster-list-item-base-left">
<div className="base-name">{itemData.name ?? '-'}</div>
<span className="base-version">{itemData.kafkaVersion ?? '-'}</span>
{loadReBalanceEnable !== undefined && (
<div
style={{
display: 'flex',
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{[
['BytesIn', loadReBalanceNwIn === 1],
['BytesOut', loadReBalanceNwOut === 1],
['Disk', loadReBalanceDisk === 1],
].map(([name, isBalanced]) => {
return isBalanced ? (
<div className="balance-box balanced">{name} </div>
) : loadReBalanceEnable ? (
<div className="balance-box unbalanced">{name} </div>
) : (
<Tooltip
title={
<span>
{name}
<Link to={`/cluster/${itemData.id}/cluster/balance`}></Link>
</span>
}
>
<div className="balance-box unbalanced">{name} </div>
</Tooltip>
);
})}
</div>
)}
</div>
<div className="multi-cluster-list-item-base-date">{moment(itemData.createTime).format(timeFormat)}</div>
</div>
<div className="multi-cluster-list-item-Indicator">
<div className="indicator-left">
<div className="indicator-left-item">
<div className="indicator-left-item-title">
<span
className="indicator-left-item-title-dot"
style={{
background: itemData.latestMetrics?.metrics?.BrokersNotAlive ? '#FF7066' : '#34C38F',
}}
></span>
Brokers
</div>
<div className="indicator-left-item-value">{brokers}</div>
</div>
<div className="indicator-left-item">
<div className="indicator-left-item-title">
<span
className="indicator-left-item-title-dot"
style={{
background: zookeepersAvailable === -1 ? '#e9e7e7' : zookeepersAvailable === 0 ? '#FF7066' : '#34C38F',
}}
></span>
ZK
</div>
<div className="indicator-left-item-value">{zookeepersAvailable === -1 ? '-' : zks}</div>
</div>
</div>
<div className="indicator-right">
{metricPoints.map((row, index) => {
return (
<div
key={row.metricName + index}
className={`indicator-right-item ${row.metricName === 'LeaderMessages' ? 'first-right-item' : ''}`}
>
<div className="indicator-right-item-total">
<div className="indicator-right-item-total-name">
{row.metricName === 'TotalLogSize' ? 'MessageSize' : row.metricName}
</div>
<div className="indicator-right-item-total-value">
{row.value}
<span className="total-value-unit">{row.unit}</span>
</div>
</div>
<div className="indicator-right-item-chart">
<SmallChart width={79} height={40} chartData={row.metricLines} />
</div>
</div>
);
})}
</div>
</div>
</div>
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_DEL) ? (
<div className="multi-cluster-list-item-btn">
<div className="icon" onClick={(event) => onClickDeleteBtn(event, itemData)}>
<IconFont type="icon-shanchu1" />
</div>
</div>
) : (
<></>
)}
</div>
</List.Item>
);
};
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(
() => (
<InfiniteScroll
dataLength={list.length}
next={loadMoreData}
hasMore={list.length < pagination.total}
loader={<Spin style={{ paddingLeft: '50%', paddingTop: 15 }} spinning={loading} />}
endMessage={
!pagination.total ? (
''
) : (
<Divider className="load-completed-tip" plain>
{pagination.total}
</Divider>
)
}
scrollableTarget="scrollableDiv"
>
<List
bordered={false}
split={false}
className="multi-cluster-list"
itemLayout="horizontal"
dataSource={list}
renderItem={RenderItem}
/>
</InfiniteScroll>
),
[list, pagination, loading]
)}
<Modal
width={570}
destroyOnClose={true}
centered={true}
className="custom-modal"
wrapClassName="del-topic-modal delete-modal"
title={intl.formatMessage({
id: 'delete.cluster.confirm.title',
})}
visible={visible}
onOk={onFinish}
okText={intl.formatMessage({
id: 'btn.delete',
})}
cancelText={intl.formatMessage({
id: 'btn.cancel',
})}
onCancel={() => setVisible(false)}
okButtonProps={{
style: {
width: 56,
},
danger: true,
size: 'small',
}}
cancelButtonProps={{
style: {
width: 56,
},
size: 'small',
}}
>
<div className="tip-info">
<IconFont type="icon-warning-circle"></IconFont>
<span>
{intl.formatMessage({
id: 'delete.cluster.confirm.tip',
})}
</span>
</div>
<Form form={form} className="form" labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} autoComplete="off">
<Form.Item label="集群名称" name="name" rules={[{ required: false, message: '' }]}>
<span>{clusterInfo.name}</span>
</Form.Item>
<Form.Item
label="集群名称"
name="clusterName"
rules={[
{
required: true,
message: intl.formatMessage({
id: 'delete.cluster.confirm.cluster',
}),
validator: (rule: any, value: string) => {
value = value || '';
if (!value.trim() || value.trim() !== clusterInfo.name)
return Promise.reject(
intl.formatMessage({
id: 'delete.cluster.confirm.cluster',
})
);
return Promise.resolve();
},
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
</>
);
};
export default ListScroll;