mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-05 21:12:13 +08:00
feat: 新增Topic 复制功能
This commit is contained in:
@@ -0,0 +1,289 @@
|
|||||||
|
// 批量Topic复制
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Button, Drawer, Form, Select, Utils, AppContainer, Space, Divider, Transfer, Checkbox, Tooltip } from 'knowdesign';
|
||||||
|
import message from '@src/components/Message';
|
||||||
|
import { IconFont } from '@knowdesign/icons';
|
||||||
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
import './index.less';
|
||||||
|
import Api from '@src/api/index';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const CheckboxGroup = Checkbox.Group;
|
||||||
|
|
||||||
|
interface DefaultConfig {
|
||||||
|
drawerVisible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
genData?: () => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props: DefaultConfig) => {
|
||||||
|
const { drawerVisible, onClose, genData } = props;
|
||||||
|
const routeParams = useParams<{ clusterId: string }>();
|
||||||
|
const [visible, setVisible] = useState(drawerVisible);
|
||||||
|
const [topicList, setTopicList] = useState([]);
|
||||||
|
const [clusterList, setClusterList] = useState([]);
|
||||||
|
const [selectTopicList, setSelectTopicList] = useState([]);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const topicsPerCluster = React.useRef({} as any);
|
||||||
|
|
||||||
|
const mirrorScopeOptions = [
|
||||||
|
{
|
||||||
|
label: '数据',
|
||||||
|
value: 'syncData',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Topic配置',
|
||||||
|
value: 'syncConfig',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'kafkauser+Acls',
|
||||||
|
value: 'kafkauserAcls',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Group Offset',
|
||||||
|
value: 'groupOffset',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getTopicList = () => {
|
||||||
|
Utils.request(Api.getTopicMetaData(+routeParams.clusterId)).then((res: any) => {
|
||||||
|
const dataDe = res || [];
|
||||||
|
const dataHandle = dataDe.map((item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
key: item.topicName,
|
||||||
|
title: item.topicName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setTopicList(dataHandle);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopicsPerCluster = (clusterId: number) => {
|
||||||
|
Utils.request(Api.getTopicMetaData(clusterId)).then((res: any) => {
|
||||||
|
const dataDe = res || [];
|
||||||
|
const dataHandle = dataDe.map((item: any) => item.topicName);
|
||||||
|
topicsPerCluster.current = { ...topicsPerCluster.current, [clusterId]: dataHandle };
|
||||||
|
setTimeout(() => {
|
||||||
|
form.validateFields(['topicNames']);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClusterList = () => {
|
||||||
|
Utils.request(Api.getMirrorClusterList()).then((res: any) => {
|
||||||
|
const dataDe = res || [];
|
||||||
|
const dataHandle = dataDe.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setClusterList(dataHandle);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkTopic = (_: any, value: any[]) => {
|
||||||
|
const clusters = form.getFieldValue('destClusterPhyIds');
|
||||||
|
if (!value || !value.length) {
|
||||||
|
return Promise.reject('请选择需要复制的Topic');
|
||||||
|
} else {
|
||||||
|
if (clusters && clusters.length) {
|
||||||
|
// 验证Topic是否存在
|
||||||
|
const existTopics = {} as any;
|
||||||
|
clusters.forEach((cluster: number) => {
|
||||||
|
if (cluster && topicsPerCluster.current[cluster]) {
|
||||||
|
existTopics[cluster] = [];
|
||||||
|
value.forEach((topic) => {
|
||||||
|
if (topicsPerCluster.current[cluster].indexOf(topic) > -1) {
|
||||||
|
existTopics[cluster].push(topic);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!existTopics[cluster].length) delete existTopics[cluster];
|
||||||
|
} else {
|
||||||
|
getTopicsPerCluster(cluster);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(existTopics).length) {
|
||||||
|
let errorInfo = '';
|
||||||
|
Object.keys(existTopics).forEach((key) => {
|
||||||
|
const clusterName = clusterList.find((item) => item.value == key)?.label;
|
||||||
|
errorInfo = errorInfo.concat(`${existTopics[key].join('、')}在集群【${clusterName}】中已存在,`);
|
||||||
|
});
|
||||||
|
errorInfo = errorInfo.concat('请重新选择');
|
||||||
|
return Promise.reject(errorInfo);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const topicChange = (val: string[]) => {
|
||||||
|
setSelectTopicList(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clusterChange = (val: number[]) => {
|
||||||
|
if (val && val.length) {
|
||||||
|
val.forEach((item) => {
|
||||||
|
if (item && !topicsPerCluster.current[item]) {
|
||||||
|
getTopicsPerCluster(item);
|
||||||
|
} else {
|
||||||
|
form.validateFields(['topicNames']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
form.validateFields(['topicNames']);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDrawerClose = () => {
|
||||||
|
form.resetFields();
|
||||||
|
setSelectTopicList([]);
|
||||||
|
topicsPerCluster.current = {};
|
||||||
|
setVisible(false);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!drawerVisible) return;
|
||||||
|
setVisible(true);
|
||||||
|
getTopicList();
|
||||||
|
getClusterList();
|
||||||
|
}, [drawerVisible]);
|
||||||
|
|
||||||
|
const addTopicMirror = () => {
|
||||||
|
form.validateFields().then((e) => {
|
||||||
|
const formData = form.getFieldsValue();
|
||||||
|
const handledData = [] as any;
|
||||||
|
formData.destClusterPhyIds.forEach((cluster: number) => {
|
||||||
|
formData.topicNames.forEach((topic: string) => {
|
||||||
|
handledData.push({
|
||||||
|
destClusterPhyId: cluster,
|
||||||
|
sourceClusterPhyId: +routeParams.clusterId,
|
||||||
|
syncData: formData.mirrorScope.indexOf('syncData') > -1,
|
||||||
|
syncConfig: formData.mirrorScope.indexOf('syncConfig') > -1,
|
||||||
|
topicName: topic,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Utils.post(Api.handleTopicMirror(), handledData).then(() => {
|
||||||
|
message.success('成功复制Topic');
|
||||||
|
onDrawerClose();
|
||||||
|
genData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
push={false}
|
||||||
|
title="Topic复制"
|
||||||
|
width={600}
|
||||||
|
placement="right"
|
||||||
|
onClose={onDrawerClose}
|
||||||
|
visible={visible}
|
||||||
|
className="topic-job-drawer"
|
||||||
|
maskClosable={false}
|
||||||
|
destroyOnClose
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
onClick={(_) => {
|
||||||
|
onDrawerClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button size="small" type="primary" onClick={addTopicMirror}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="wrap">
|
||||||
|
<Form form={form} layout="vertical" className="task-form">
|
||||||
|
<Form.Item name="topicNames" label="选择Topic" rules={[{ required: true, validator: checkTopic }]}>
|
||||||
|
<Transfer
|
||||||
|
dataSource={topicList}
|
||||||
|
showSearch
|
||||||
|
filterOption={(inputValue, option) => option.topicName.indexOf(inputValue) > -1}
|
||||||
|
targetKeys={selectTopicList}
|
||||||
|
onChange={topicChange}
|
||||||
|
render={(item) => item.title}
|
||||||
|
titles={['待选Topic', '已选Topic']}
|
||||||
|
customHeader
|
||||||
|
showSelectedCount
|
||||||
|
locale={{ itemUnit: '', itemsUnit: '' }}
|
||||||
|
suffix={<IconFont type="icon-fangdajing" />}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="destClusterPhyIds"
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<span style={{ marginRight: '8px' }}>选择目标集群</span>
|
||||||
|
<Tooltip title="目标集群需要为使用滴滴kafka的350+版本的kafka集群">
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
rules={[{ required: true, message: '请选择目标集群' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择目标集群,可多选"
|
||||||
|
mode="multiple"
|
||||||
|
maxTagCount={'responsive'}
|
||||||
|
allowClear
|
||||||
|
onChange={clusterChange}
|
||||||
|
options={clusterList.filter((item) => item.value !== +routeParams.clusterId)}
|
||||||
|
></Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="mirrorScope"
|
||||||
|
label="Topic复制范围"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (_: any, value: any[]) => {
|
||||||
|
if (!value || !value.length) {
|
||||||
|
return Promise.reject('请选择Topic复制范围');
|
||||||
|
} else if (value.indexOf('syncData') === -1) {
|
||||||
|
return Promise.reject('Topic复制范围必须选择[数据]');
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
initialValue={['syncData']}
|
||||||
|
>
|
||||||
|
<CheckboxGroup className="checkbox-content-margin">
|
||||||
|
{mirrorScopeOptions.map((option) => {
|
||||||
|
return option.disabled ? (
|
||||||
|
<Tooltip title="当前版本暂不支持">
|
||||||
|
<Checkbox key={option.value} value={option.value} disabled>
|
||||||
|
{option.label}
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Checkbox key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CheckboxGroup>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { AppContainer, ProTable, Utils, Tag, Modal, Tooltip } from 'knowdesign';
|
||||||
|
import Api from '@src/api';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { getDataUnit } from '@src/constants/chartConfig';
|
||||||
|
import message from '@src/components/Message';
|
||||||
|
import { ClustersPermissionMap } from '../CommonConfig';
|
||||||
|
import { ControlStatusMap } from '../CommonRoute';
|
||||||
|
const { request } = Utils;
|
||||||
|
|
||||||
|
const getColmns = (arg: any) => {
|
||||||
|
const formattedBytes = (v: number) => {
|
||||||
|
const [unit, size] = getDataUnit['Memory'](v);
|
||||||
|
return `${(v / size).toFixed(2)}${unit}/s`;
|
||||||
|
};
|
||||||
|
const tagEle = (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
color: '#5664FF',
|
||||||
|
padding: '2px 5px',
|
||||||
|
background: '#eff1fd',
|
||||||
|
marginLeft: '-4px',
|
||||||
|
transform: 'scale(0.83,0.83)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
当前集群
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
const baseColumns: any = [
|
||||||
|
{
|
||||||
|
title: '源集群',
|
||||||
|
dataIndex: 'sourceClusterName',
|
||||||
|
key: 'sourceClusterName',
|
||||||
|
render: (t: string, record: any) => (
|
||||||
|
<>
|
||||||
|
<span>{t || '-'}</span>
|
||||||
|
{record.sourceClusterId == arg.clusterId && tagEle}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '目标集群',
|
||||||
|
dataIndex: 'destClusterName',
|
||||||
|
key: 'destClusterName',
|
||||||
|
render: (t: string, record: any) => (
|
||||||
|
<>
|
||||||
|
<span>{t || '-'}</span>
|
||||||
|
{record.destClusterId == arg.clusterId && tagEle}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '消息写入速率',
|
||||||
|
dataIndex: 'bytesIn',
|
||||||
|
key: 'bytesIn',
|
||||||
|
width: 150,
|
||||||
|
render: (t: number) => (t !== null && t !== undefined ? formattedBytes(t) : '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '消息复制速率',
|
||||||
|
dataIndex: 'replicationBytesIn',
|
||||||
|
key: 'replicationBytesIn',
|
||||||
|
width: 150,
|
||||||
|
render: (t: number) => (t !== null && t !== undefined ? formattedBytes(t) : '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '延迟(个消息)',
|
||||||
|
dataIndex: 'lag',
|
||||||
|
key: 'lag',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'option',
|
||||||
|
key: 'option',
|
||||||
|
width: 100,
|
||||||
|
render: (_t: any, r: any) => {
|
||||||
|
return arg.global.hasPermission(ClustersPermissionMap.TOPIC_CANCEL_REPLICATOR) ? (
|
||||||
|
<a onClick={() => arg.cancelSync(r)}>取消同步</a>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return baseColumns;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Replicator = (props: any) => {
|
||||||
|
const { hashData } = props;
|
||||||
|
const urlParams = useParams<any>(); // 获取地址栏参数
|
||||||
|
const [global] = AppContainer.useGlobalValue();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [pagination, setPagination] = useState<any>({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
position: 'bottomRight',
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const genData = () => {
|
||||||
|
if (urlParams?.clusterId === undefined || hashData?.topicName === undefined) return;
|
||||||
|
setLoading(true);
|
||||||
|
request(Api.getTopicMirrorList(urlParams?.clusterId, hashData?.topicName))
|
||||||
|
.then((res: any = []) => {
|
||||||
|
setData(res);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelSync = (item: any) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: `确认取消此Topic同步吗?`,
|
||||||
|
okType: 'primary',
|
||||||
|
centered: true,
|
||||||
|
okButtonProps: {
|
||||||
|
size: 'small',
|
||||||
|
danger: true,
|
||||||
|
},
|
||||||
|
cancelButtonProps: {
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
maskClosable: false,
|
||||||
|
onOk(close) {
|
||||||
|
close();
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
destClusterPhyId: item.destClusterId,
|
||||||
|
sourceClusterPhyId: item.sourceClusterId,
|
||||||
|
topicName: item.topicName,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
Utils.delete(Api.handleTopicMirror(), { data }).then(() => {
|
||||||
|
message.success('成功取消Topic同步');
|
||||||
|
genData();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTableChange = (pagination: any, filters: any, sorter: any, extra: any) => {
|
||||||
|
setPagination(pagination);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
props.positionType === 'Replicator' && genData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pro-table-wrap">
|
||||||
|
<ProTable
|
||||||
|
showQueryForm={false}
|
||||||
|
tableProps={{
|
||||||
|
loading,
|
||||||
|
showHeader: false,
|
||||||
|
rowKey: 'unique',
|
||||||
|
columns: getColmns({ global, cancelSync, clusterId: urlParams?.clusterId }),
|
||||||
|
dataSource: data,
|
||||||
|
paginationProps: pagination,
|
||||||
|
attrs: {
|
||||||
|
onChange: onTableChange,
|
||||||
|
scroll: { y: 'calc(100vh - 400px)' },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Replicator;
|
||||||
@@ -10,6 +10,7 @@ import ConsumerGroups from './ConsumerGroups';
|
|||||||
import ACLs from './ACLs';
|
import ACLs from './ACLs';
|
||||||
import Configuration from './Configuration';
|
import Configuration from './Configuration';
|
||||||
import Consumers from './ConsumerGroups';
|
import Consumers from './ConsumerGroups';
|
||||||
|
import Replicator from './Replicator';
|
||||||
// import Consumers from '@src/pages/Consumers';
|
// import Consumers from '@src/pages/Consumers';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import TopicDetailHealthCheck from '@src/components/CardBar/TopicDetailHealthCheck';
|
import TopicDetailHealthCheck from '@src/components/CardBar/TopicDetailHealthCheck';
|
||||||
@@ -206,6 +207,9 @@ const TopicDetail = (props: any) => {
|
|||||||
<Configuration searchKeywords={searchKeywords} positionType={positionType} hashData={hashData} />
|
<Configuration searchKeywords={searchKeywords} positionType={positionType} hashData={hashData} />
|
||||||
)}
|
)}
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
<TabPane tab="Replicator" key="Replicator">
|
||||||
|
{positionType === 'Replicator' && <Replicator searchKeywords={searchKeywords} positionType={positionType} hashData={hashData} />}
|
||||||
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import { AppContainer, Input, ProTable, Select, Switch, Tooltip, Utils, Dropdown, Menu, Button, Divider } from 'knowdesign';
|
import { AppContainer, Input, ProTable, Select, Switch, Tooltip, Utils, Dropdown, Menu, Button, Divider, Tag } from 'knowdesign';
|
||||||
import { IconFont } from '@knowdesign/icons';
|
import { IconFont } from '@knowdesign/icons';
|
||||||
import Create from './Create';
|
import Create from './Create';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
@@ -15,10 +15,12 @@ import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
|
|||||||
import ReplicaChange from '@src/components/TopicJob/ReplicaChange';
|
import ReplicaChange from '@src/components/TopicJob/ReplicaChange';
|
||||||
import SmallChart from '@src/components/SmallChart';
|
import SmallChart from '@src/components/SmallChart';
|
||||||
import ReplicaMove from '@src/components/TopicJob/ReplicaMove';
|
import ReplicaMove from '@src/components/TopicJob/ReplicaMove';
|
||||||
|
import TopicMirror from '@src/components/TopicJob/TopicMirror';
|
||||||
import { formatAssignSize } from '../Jobs/config';
|
import { formatAssignSize } from '../Jobs/config';
|
||||||
import { DownOutlined } from '@ant-design/icons';
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
import { tableHeaderPrefix } from '@src/constants/common';
|
import { tableHeaderPrefix } from '@src/constants/common';
|
||||||
import { HealthStateMap } from './config';
|
import { HealthStateMap } from './config';
|
||||||
|
import { ControlStatusMap } from '../CommonRoute';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ const AutoPage = (props: any) => {
|
|||||||
const [type, setType] = useState<string>('');
|
const [type, setType] = useState<string>('');
|
||||||
const [changeVisible, setChangeVisible] = useState(false);
|
const [changeVisible, setChangeVisible] = useState(false);
|
||||||
const [moveVisible, setMoveVisible] = useState(false);
|
const [moveVisible, setMoveVisible] = useState(false);
|
||||||
|
const [mirrorVisible, setMirrorVisible] = useState(false);
|
||||||
const [selectValue, setSelectValue] = useState('批量操作');
|
const [selectValue, setSelectValue] = useState('批量操作');
|
||||||
|
|
||||||
const [sortObj, setSortObj] = useState<{
|
const [sortObj, setSortObj] = useState<{
|
||||||
@@ -131,8 +134,9 @@ const AutoPage = (props: any) => {
|
|||||||
className: 'clean-padding-left',
|
className: 'clean-padding-left',
|
||||||
lineClampOne: true,
|
lineClampOne: true,
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
render: (t: string, r: any) => {
|
render: (t: string, record: any) => {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Tooltip title={t}>
|
<Tooltip title={t}>
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -142,6 +146,22 @@ const AutoPage = (props: any) => {
|
|||||||
{t}
|
{t}
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{record.inMirror && (
|
||||||
|
<div>
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
color: '#5664FF',
|
||||||
|
padding: '2px 5px',
|
||||||
|
background: '#eff1fd',
|
||||||
|
marginLeft: '-4px',
|
||||||
|
transform: 'scale(0.83,0.83)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
复制中...
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -256,6 +276,7 @@ const AutoPage = (props: any) => {
|
|||||||
const onclose = () => {
|
const onclose = () => {
|
||||||
setChangeVisible(false);
|
setChangeVisible(false);
|
||||||
setMoveVisible(false);
|
setMoveVisible(false);
|
||||||
|
setMirrorVisible(false);
|
||||||
setSelectValue('批量操作');
|
setSelectValue('批量操作');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,6 +292,11 @@ const AutoPage = (props: any) => {
|
|||||||
<a onClick={() => setMoveVisible(true)}>迁移副本</a>
|
<a onClick={() => setMoveVisible(true)}>迁移副本</a>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
|
{global.hasPermission(ClustersPermissionMap.TOPIC_REPLICATOR) && (
|
||||||
|
<Menu.Item>
|
||||||
|
<a onClick={() => setMirrorVisible(true)}>Topic复制</a>
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -296,6 +322,8 @@ const AutoPage = (props: any) => {
|
|||||||
<ReplicaChange drawerVisible={changeVisible} jobId={''} topics={selectedRowKeys} onClose={onclose}></ReplicaChange>
|
<ReplicaChange drawerVisible={changeVisible} jobId={''} topics={selectedRowKeys} onClose={onclose}></ReplicaChange>
|
||||||
{/* 批量迁移 */}
|
{/* 批量迁移 */}
|
||||||
<ReplicaMove drawerVisible={moveVisible} jobId={''} topics={selectedRowKeys} onClose={onclose}></ReplicaMove>
|
<ReplicaMove drawerVisible={moveVisible} jobId={''} topics={selectedRowKeys} onClose={onclose}></ReplicaMove>
|
||||||
|
{/* Topic复制 */}
|
||||||
|
<TopicMirror drawerVisible={mirrorVisible} genData={getTopicsList} onClose={onclose}></TopicMirror>
|
||||||
|
|
||||||
<div className={`${tableHeaderPrefix}-left-refresh`} onClick={() => getTopicsList()}>
|
<div className={`${tableHeaderPrefix}-left-refresh`} onClick={() => getTopicsList()}>
|
||||||
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
|
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
|
||||||
@@ -334,7 +362,8 @@ const AutoPage = (props: any) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{(global.hasPermission(ClustersPermissionMap.TOPIC_CHANGE_REPLICA) ||
|
{(global.hasPermission(ClustersPermissionMap.TOPIC_CHANGE_REPLICA) ||
|
||||||
global.hasPermission(ClustersPermissionMap.TOPIC_MOVE_REPLICA)) && (
|
global.hasPermission(ClustersPermissionMap.TOPIC_MOVE_REPLICA) ||
|
||||||
|
global.hasPermission(ClustersPermissionMap.TOPIC_REPLICATOR)) && (
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
<Dropdown overlay={menu} trigger={['click']}>
|
||||||
<Button className="batch-btn" icon={<DownOutlined />} type="primary" ghost>
|
<Button className="batch-btn" icon={<DownOutlined />} type="primary" ghost>
|
||||||
批量变更
|
批量变更
|
||||||
|
|||||||
Reference in New Issue
Block a user