mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-13 11:32:20 +08:00
V3.2
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
import api from '@src/api';
|
||||
import CodeMirrorFormItem from '@src/components/CodeMirrorFormItem';
|
||||
import customMessage from '@src/components/Message';
|
||||
import { Button, Divider, Drawer, Form, message, Space, Utils } from 'knowdesign';
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { ConnectCluster, ConnectorPlugin, ConnectorPluginConfig, OperateInfo } from './AddConnector';
|
||||
|
||||
const PLACEHOLDER = `配置格式如下
|
||||
|
||||
{
|
||||
"connectClusterName": "", // Connect Cluster 名称
|
||||
"configs": { // 具体配置项
|
||||
"name": "",
|
||||
"connector.class": "",
|
||||
"tasks.max": 1,
|
||||
...
|
||||
}
|
||||
}`;
|
||||
|
||||
export default forwardRef((props: any, ref) => {
|
||||
const { clusterId } = useParams<{
|
||||
clusterId: string;
|
||||
}>();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [type, setType] = useState('create');
|
||||
const [connectClusters, setConnectClusters] = useState<{ label: string; value: number }[]>([]);
|
||||
const [defaultConfigs, setDefaultConfigs] = useState<{ [key: string]: any }>({});
|
||||
const [submitLoading, setSubmitLoading] = useState(false);
|
||||
|
||||
const getConnectClusters = () => {
|
||||
return Utils.request(api.getConnectClusters(clusterId)).then((res: ConnectCluster[]) => {
|
||||
setConnectClusters(
|
||||
res.map(({ name, id }) => ({
|
||||
label: name || '-',
|
||||
value: id,
|
||||
}))
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const onOpen = (type: 'create' | 'edit', connectClusterName?: string, defaultConfigs?: { [key: string]: any }) => {
|
||||
if (defaultConfigs) {
|
||||
setDefaultConfigs({ ...defaultConfigs, connectClusterName });
|
||||
form.setFieldsValue({
|
||||
configs: JSON.stringify(
|
||||
{
|
||||
connectClusterName,
|
||||
configs: defaultConfigs,
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
});
|
||||
}
|
||||
setType(type);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
setSubmitLoading(true);
|
||||
form.validateFields().then(
|
||||
(data) => {
|
||||
const postData = JSON.parse(data.configs);
|
||||
postData.connectorName = postData.configs.name;
|
||||
postData.connectClusterId = connectClusters.find((cluster) => cluster.label === postData.connectClusterName).value;
|
||||
delete postData.connectClusterName;
|
||||
|
||||
Object.entries(postData.configs).forEach(([key, val]) => {
|
||||
if (val === null) {
|
||||
delete postData.configs[key];
|
||||
}
|
||||
});
|
||||
Utils.put(api.validateConnectorConfig, postData).then(
|
||||
(res: ConnectorPluginConfig) => {
|
||||
if (res) {
|
||||
if (res?.errorCount > 0) {
|
||||
const errors: OperateInfo['errors'] = {};
|
||||
res?.configs
|
||||
?.filter((config) => config.value.errors.length !== 0)
|
||||
.forEach(({ value }) => {
|
||||
if (value.name.includes('transforms.')) {
|
||||
errors['transforms'] = (errors['transforms'] || []).concat(value.errors);
|
||||
} else {
|
||||
errors[value.name] = value.errors;
|
||||
}
|
||||
});
|
||||
form.setFields([
|
||||
{
|
||||
name: 'configs',
|
||||
errors: Object.entries(errors).map(([name, errorArr]) => `${name}: ${errorArr.join('; ')}\n`),
|
||||
},
|
||||
]);
|
||||
setSubmitLoading(false);
|
||||
} else {
|
||||
if (type === 'create') {
|
||||
Utils.post(api.connectorsOperates, postData)
|
||||
.then(() => {
|
||||
customMessage.success('新建成功');
|
||||
onClose();
|
||||
props?.refresh();
|
||||
})
|
||||
.finally(() => setSubmitLoading(false));
|
||||
} else {
|
||||
Utils.put(api.updateConnectorConfig, postData)
|
||||
.then(() => {
|
||||
customMessage.success('编辑成功');
|
||||
props?.refresh();
|
||||
onClose();
|
||||
})
|
||||
.finally(() => setSubmitLoading(false));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setSubmitLoading(false);
|
||||
message.error('接口校验出错,请重新提交');
|
||||
}
|
||||
},
|
||||
() => setSubmitLoading(false)
|
||||
);
|
||||
},
|
||||
() => setSubmitLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getConnectClusters();
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onOpen,
|
||||
onClose,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={`${type === 'create' ? '新建' : '编辑'} Connector`}
|
||||
className="operate-connector-drawer-use-json"
|
||||
width={800}
|
||||
visible={visible}
|
||||
onClose={onClose}
|
||||
maskClosable={false}
|
||||
extra={
|
||||
<div className="operate-wrap">
|
||||
<Space>
|
||||
<Button size="small" onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button size="small" type="primary" onClick={onSubmit} loading={submitLoading}>
|
||||
确定
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="configs"
|
||||
validateTrigger="onBlur"
|
||||
rules={[
|
||||
{
|
||||
validator(rule, value) {
|
||||
if (!value) {
|
||||
return Promise.reject('配置不能为空');
|
||||
}
|
||||
try {
|
||||
const v = JSON.parse(value);
|
||||
if (typeof v !== 'object') {
|
||||
return Promise.reject('输入内容必须为 JSON');
|
||||
}
|
||||
let connectClusterId = -1;
|
||||
// 校验 connectClusterName 字段
|
||||
if (!v.connectClusterName) {
|
||||
return Promise.reject('内容缺少 connectClusterName 字段或字段内容为空');
|
||||
} else {
|
||||
if (type === 'edit') {
|
||||
if (v.connectClusterName !== defaultConfigs.connectClusterName) {
|
||||
return Promise.reject('编辑模式下不允许修改 connectClusterName 字段');
|
||||
}
|
||||
} else {
|
||||
if (!connectClusters.length) {
|
||||
getConnectClusters();
|
||||
return Promise.reject('connectClusterName 列表获取失败,请重试');
|
||||
}
|
||||
const targetConnectCluster = connectClusters.find((cluster) => cluster.label === v.connectClusterName);
|
||||
if (!targetConnectCluster) {
|
||||
return Promise.reject('connectClusterName 不存在,请检查');
|
||||
} else {
|
||||
connectClusterId = targetConnectCluster.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!v.configs || typeof v.configs !== 'object') {
|
||||
return Promise.reject('内容缺少 configs 字段或字段格式错误');
|
||||
} else {
|
||||
// 校验 connectorName 字段
|
||||
if (!v.configs.name) {
|
||||
return Promise.reject('configs 字段下缺少 name 项');
|
||||
} else {
|
||||
if (type === 'edit' && v.configs.name !== defaultConfigs.name) {
|
||||
return Promise.reject('编辑模式下不允许修改 name 字段');
|
||||
}
|
||||
}
|
||||
if (!v.configs['connector.class']) {
|
||||
return Promise.reject('configs 字段下缺少 connector.class 项');
|
||||
} else if (type === 'edit' && v.configs['connector.class'] !== defaultConfigs['connector.class']) {
|
||||
return Promise.reject('编辑模式下不允许修改 connector.class 字段');
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'create') {
|
||||
// 异步校验 connector 名称是否重复 以及 className 是否存在
|
||||
return Promise.all([
|
||||
Utils.request(api.isConnectorExist(connectClusterId, v.configs.name)),
|
||||
Utils.request(api.getConnectorPlugins(connectClusterId)),
|
||||
]).then(
|
||||
([data, plugins]: [any, ConnectorPlugin[]]) => {
|
||||
return data?.exist
|
||||
? Promise.reject('name 与已有 Connector 重复')
|
||||
: plugins.every((plugin) => plugin.className !== v.configs['connector.class'])
|
||||
? Promise.reject('该 connectCluster 下不存在 connector.class 项配置的插件')
|
||||
: Promise.resolve();
|
||||
},
|
||||
() => {
|
||||
return Promise.reject('接口校验出错,请重试');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch (e) {
|
||||
return Promise.reject('输入内容必须为 JSON');
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
{visible && (
|
||||
<div>
|
||||
<CodeMirrorFormItem
|
||||
resize
|
||||
defaultInput={form.getFieldValue('configs')}
|
||||
placeholder={PLACEHOLDER}
|
||||
onBeforeChange={(configs: string) => {
|
||||
form.setFieldsValue({ configs });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Drawer>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Button, Form, Input, Modal, Utils } from 'knowdesign';
|
||||
import notification from '@src/components/Notification';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import Api from '@src/api/index';
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const DeleteConnector = (props: { record: any; onConfirm?: () => void }) => {
|
||||
const { record, onConfirm } = props;
|
||||
const [form] = Form.useForm();
|
||||
const [delDialogVisible, setDelDialogVisble] = useState(false);
|
||||
const handleDelOk = () => {
|
||||
form.validateFields().then((e) => {
|
||||
const formVal = form.getFieldsValue();
|
||||
formVal.connectClusterId = Number(record.connectClusterId);
|
||||
Utils.delete(Api.connectorsOperates, { data: formVal }).then((res: any) => {
|
||||
if (res === null) {
|
||||
notification.success({
|
||||
message: '删除成功',
|
||||
});
|
||||
setDelDialogVisble(false);
|
||||
onConfirm && onConfirm();
|
||||
} else {
|
||||
notification.error({
|
||||
message: '删除失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={(_) => {
|
||||
setDelDialogVisble(true);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Modal
|
||||
className="custom-modal"
|
||||
title="确定删除此Connector吗?"
|
||||
centered={true}
|
||||
visible={delDialogVisible}
|
||||
wrapClassName="del-connect-modal"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
onOk={handleDelOk}
|
||||
onCancel={(_) => {
|
||||
setDelDialogVisble(false);
|
||||
}}
|
||||
okText="删除"
|
||||
okButtonProps={{
|
||||
danger: true,
|
||||
size: 'small',
|
||||
style: {
|
||||
paddingLeft: '16px',
|
||||
paddingRight: '16px',
|
||||
},
|
||||
}}
|
||||
cancelButtonProps={{
|
||||
size: 'small',
|
||||
style: {
|
||||
paddingLeft: '16px',
|
||||
paddingRight: '16px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Form form={form} labelCol={{ span: 6 }} style={{ marginTop: 17 }}>
|
||||
<Form.Item label="ConnectorName">{record.connectorName}</Form.Item>
|
||||
<Form.Item
|
||||
name="connectorName"
|
||||
label="ConnectorName"
|
||||
rules={[
|
||||
// { required: true },
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error('请输入ConnectorName名称'));
|
||||
} else if (value !== record.connectorName) {
|
||||
return Promise.reject(new Error('请输入正确的ConnectorName名称'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入" size="small"></Input>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteConnector;
|
||||
@@ -0,0 +1,111 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Drawer, Utils, AppContainer, ProTable } from 'knowdesign';
|
||||
import API from '@src/api';
|
||||
import ConnectDetailCard from '@src/components/CardBar/ConnectDetailCard';
|
||||
import { defaultPagination, getConnectorsDetailColumns } from './config';
|
||||
import notification from '@src/components/Notification';
|
||||
import './index.less';
|
||||
|
||||
const prefix = 'connect-detail';
|
||||
const { request } = Utils;
|
||||
const ConnectorDetail = (props: any) => {
|
||||
const { visible, setVisible, record } = props;
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState([]);
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
setPagination(defaultPagination);
|
||||
// clean hash
|
||||
};
|
||||
|
||||
// 请求接口获取数据
|
||||
const genData = async () => {
|
||||
if (global?.clusterInfo?.id === undefined) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
request(API.getConnectDetailTasks(record.connectorName, record.connectClusterId))
|
||||
.then((res: any) => {
|
||||
setData(res || []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||
setPagination(pagination);
|
||||
};
|
||||
|
||||
const optionFn: any = (taskId: any) => {
|
||||
const params = {
|
||||
action: 'restart',
|
||||
connectClusterId: record?.connectClusterId,
|
||||
connectorName: record?.connectorName,
|
||||
taskId,
|
||||
};
|
||||
|
||||
request(API.optionTasks(), { method: 'PUT', data: params }).then((res: any) => {
|
||||
if (res === null) {
|
||||
notification.success({
|
||||
message: `任务重试成功`,
|
||||
});
|
||||
genData();
|
||||
} else {
|
||||
notification.error({
|
||||
message: `任务重试失败`,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const retryOption = Utils.useDebounce(optionFn, 500);
|
||||
|
||||
useEffect(() => {
|
||||
visible && record && genData();
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
// push={false}
|
||||
title={
|
||||
<span>
|
||||
<span style={{ fontSize: '18px', fontFamily: 'PingFangSC-Semibold', color: '#495057' }}>{record.connectorName ?? '-'}</span>
|
||||
</span>
|
||||
}
|
||||
width={1080}
|
||||
placement="right"
|
||||
onClose={onClose}
|
||||
visible={visible}
|
||||
className={`${prefix}-drawer`}
|
||||
destroyOnClose
|
||||
maskClosable={false}
|
||||
>
|
||||
<ConnectDetailCard record={record} />
|
||||
<div className={`${prefix}-drawer-title`}>Tasks</div>
|
||||
<ProTable
|
||||
key="connector-detail-table"
|
||||
showQueryForm={false}
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
rowKey: 'taskId',
|
||||
loading: loading,
|
||||
columns: getConnectorsDetailColumns({ retryOption }),
|
||||
dataSource: data,
|
||||
paginationProps: { ...pagination },
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
// scroll: { x: 'max-content' },
|
||||
bordered: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <BrokerDetailHealthCheck record={{ brokerId: hashData?.brokerId }} /> */}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectorDetail;
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, { useLayoutEffect, useState } from 'react';
|
||||
import api from '@src/api';
|
||||
import { Spin, Utils } from 'knowdesign';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import NodataImg from '@src/assets/no-data.png';
|
||||
|
||||
interface Props {
|
||||
children: any;
|
||||
}
|
||||
|
||||
const NoConnector = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 'calc(100vh - 118px)',
|
||||
boxShadow: '0 2px 4px 0 rgba(0,0,0,0.01), 0 3px 6px 3px rgba(0,0,0,0.01), 0 2px 6px 0 rgba(0,0,0,0.03)',
|
||||
borderRadius: 12,
|
||||
background: '#fff',
|
||||
}}
|
||||
>
|
||||
<img src={NodataImg} style={{ width: 100, height: 162 }} />
|
||||
<span style={{ fontSize: 13, color: '#919AAC', paddingTop: 16 }}>暂无数据,请先接入 Connect 集群</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default (props: Props) => {
|
||||
const { clusterId } = useParams<{
|
||||
clusterId: string;
|
||||
}>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
Utils.request(api.getConnectors(clusterId))
|
||||
.then((res: any[]) => {
|
||||
res?.length && setDisabled(false);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
return disabled ? (
|
||||
<Spin spinning={loading}>{loading ? <div style={{ height: 'calc(100vh - 118px)' }} /> : <NoConnector />}</Spin>
|
||||
) : (
|
||||
props.children
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
import { useParams, useHistory, useLocation } from 'react-router-dom';
|
||||
import { ProTable, Button, Utils, AppContainer, SearchInput } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import API from '../../api';
|
||||
import { getWorkersColumns, defaultPagination } from './config';
|
||||
import { tableHeaderPrefix } from '@src/constants/common';
|
||||
import ConnectCard from '@src/components/CardBar/ConnectCard';
|
||||
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
|
||||
import './index.less';
|
||||
import HasConnector from './HasConnector';
|
||||
const { request } = Utils;
|
||||
|
||||
const Workers: React.FC = () => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState([]);
|
||||
const [searchKeywords, setSearchKeywords] = useState('');
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
|
||||
// 请求接口获取数据
|
||||
const genData = async ({ pageNo, pageSize, filters, sorter }: any) => {
|
||||
if (global?.clusterInfo?.id === undefined) return;
|
||||
|
||||
setLoading(true);
|
||||
const params = {
|
||||
searchKeywords: searchKeywords.slice(0, 128),
|
||||
pageNo,
|
||||
pageSize,
|
||||
};
|
||||
|
||||
request(API.getWorkersList(global?.clusterInfo?.id), { params })
|
||||
.then((res: any) => {
|
||||
setPagination({
|
||||
current: res.pagination?.pageNo,
|
||||
pageSize: res.pagination?.pageSize,
|
||||
total: res.pagination?.total,
|
||||
});
|
||||
setData(res?.bizData || []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
genData({
|
||||
pageNo: 1,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}, [searchKeywords]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
|
||||
<DBreadcrumb
|
||||
breadcrumbs={[
|
||||
{ label: '多集群管理', aHref: '/' },
|
||||
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
|
||||
{ label: 'Connect', aHref: `/cluster/${global?.clusterInfo?.id}/connect` },
|
||||
{ label: 'Workers', aHref: `` },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<HasConnector>
|
||||
<>
|
||||
<div style={{ margin: '12px 0' }}>
|
||||
<ConnectCard />
|
||||
</div>
|
||||
<div className="custom-table-content">
|
||||
<div className={tableHeaderPrefix}>
|
||||
<div className={`${tableHeaderPrefix}-left`}>
|
||||
<div
|
||||
className={`${tableHeaderPrefix}-left-refresh`}
|
||||
onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
|
||||
>
|
||||
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${tableHeaderPrefix}-right`}>
|
||||
<SearchInput
|
||||
onSearch={setSearchKeywords}
|
||||
attrs={{
|
||||
placeholder: '请输入Host',
|
||||
style: { width: '248px', borderRiadus: '8px' },
|
||||
maxLength: 128,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ProTable
|
||||
key="workers-table"
|
||||
showQueryForm={false}
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
rowKey: 'workers_list',
|
||||
loading: loading,
|
||||
columns: getWorkersColumns(),
|
||||
dataSource: data,
|
||||
paginationProps: { ...pagination },
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
scroll: { y: 'calc(100vh - 400px)' },
|
||||
bordered: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</HasConnector>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Workers;
|
||||
@@ -0,0 +1,364 @@
|
||||
import SmallChart from '@src/components/SmallChart';
|
||||
import TagsWithHide from '@src/components/TagsWithHide';
|
||||
import { Button, Tag, Tooltip, Utils, Popconfirm } from 'knowdesign';
|
||||
import React from 'react';
|
||||
import Delete from './Delete';
|
||||
export const defaultPagination = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
position: 'bottomRight',
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
|
||||
};
|
||||
|
||||
export const optionType: { [name: string]: string } = {
|
||||
['stop']: '暂停',
|
||||
['restart']: '重启',
|
||||
['resume']: '继续',
|
||||
};
|
||||
|
||||
export const stateEnum: any = {
|
||||
['UNASSIGNED']: {
|
||||
// 未分配
|
||||
name: 'Unassigned',
|
||||
color: '#556EE6',
|
||||
bgColor: '#EBEEFA',
|
||||
},
|
||||
['RUNNING']: {
|
||||
// 运行
|
||||
name: 'Running',
|
||||
color: '#00C0A2',
|
||||
bgColor: 'rgba(0,192,162,0.10)',
|
||||
},
|
||||
['PAUSED']: {
|
||||
// 暂停
|
||||
name: 'Paused',
|
||||
color: '#495057',
|
||||
bgColor: '#ECECF6',
|
||||
},
|
||||
['FAILED']: {
|
||||
// 失败
|
||||
name: 'Failed',
|
||||
color: '#F58342',
|
||||
bgColor: '#fef3e5',
|
||||
},
|
||||
['DESTROYED']: {
|
||||
// 销毁
|
||||
name: 'Destroyed',
|
||||
color: '#FF7066',
|
||||
bgColor: '#fdefee',
|
||||
},
|
||||
['RESTARTING']: {
|
||||
// 重新启动
|
||||
name: 'Restarting',
|
||||
color: '#3991FF',
|
||||
bgColor: '#e9f5ff',
|
||||
},
|
||||
};
|
||||
|
||||
const calcCurValue = (record: any, metricName: string) => {
|
||||
// const item = (record.metricPoints || []).find((item: any) => item.metricName === metricName);
|
||||
// return item?.value || '';
|
||||
// TODO 替换record
|
||||
const orgVal = record?.latestMetrics?.metrics?.[metricName];
|
||||
if (orgVal !== undefined) {
|
||||
if (metricName === 'TotalRecordErrors') {
|
||||
return Math.round(orgVal).toLocaleString();
|
||||
} else {
|
||||
return Number(Utils.formatAssignSize(orgVal, 'KB', orgVal > 1000 ? 2 : 3)).toLocaleString();
|
||||
// return Utils.formatAssignSize(orgVal, 'KB');
|
||||
}
|
||||
}
|
||||
return '-';
|
||||
// return orgVal !== undefined ? (metricName !== 'HealthScore' ? formatAssignSize(orgVal, 'KB') : orgVal) : '-';
|
||||
};
|
||||
|
||||
const renderLine = (record: any, metricName: string) => {
|
||||
const points = record.metricLines?.find((item: any) => item.metricName === metricName)?.metricPoints || [];
|
||||
return points.length ? (
|
||||
<div className="metric-data-wrap">
|
||||
<SmallChart
|
||||
width={'100%'}
|
||||
height={30}
|
||||
chartData={{
|
||||
name: record.metricName,
|
||||
data: points.map((item: any) => ({ time: item.timeStamp, value: item.value })),
|
||||
}}
|
||||
/>
|
||||
<span className="cur-val">{calcCurValue(record, metricName)}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="cur-val">{calcCurValue(record, metricName)}</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const getConnectorsColumns = (arg?: any) => {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Connect集群',
|
||||
dataIndex: 'connectClusterName',
|
||||
key: 'connectClusterName',
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
lineClampOne: true,
|
||||
needTooltip: true,
|
||||
// render: (t: string, r: any) => {
|
||||
// return (
|
||||
// <span>
|
||||
// {t}
|
||||
// {r?.status ? <Tag className="tag-success">Live</Tag> : <Tag className="tag-error">Down</Tag>}
|
||||
// </span>
|
||||
// );
|
||||
// },
|
||||
},
|
||||
{
|
||||
title: 'Connector Name',
|
||||
dataIndex: 'connectorName',
|
||||
key: 'connectorName',
|
||||
width: 160,
|
||||
lineClampOne: true,
|
||||
render: (t: string, r: any) => {
|
||||
return t ? (
|
||||
<>
|
||||
<Tooltip placement="bottom" title={t}>
|
||||
<a
|
||||
onClick={() => {
|
||||
arg.getDetailInfo(r);
|
||||
}}
|
||||
>
|
||||
{t}
|
||||
</a>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'State',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
width: 120,
|
||||
render: (t: string, r: any) => {
|
||||
return t ? (
|
||||
<Tag
|
||||
style={{
|
||||
background: stateEnum[t]?.bgColor,
|
||||
color: stateEnum[t]?.color,
|
||||
padding: '3px 6px',
|
||||
}}
|
||||
>
|
||||
{stateEnum[t]?.name}
|
||||
</Tag>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Class',
|
||||
dataIndex: 'connectorClassName',
|
||||
key: 'connectorClassName',
|
||||
width: 150,
|
||||
lineClampOne: true,
|
||||
needTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
dataIndex: 'connectorType',
|
||||
key: 'connectorType',
|
||||
width: 100,
|
||||
render: (value: any, record: any) => Utils.firstCharUppercase(value),
|
||||
},
|
||||
{
|
||||
title: 'Tasks',
|
||||
dataIndex: 'taskCount',
|
||||
key: 'taskCount',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '消息读取速率(KB/s)',
|
||||
dataIndex: 'readRate',
|
||||
key: 'readRate',
|
||||
sorter: true,
|
||||
width: 170,
|
||||
render: (value: any, record: any) =>
|
||||
renderLine(record, record.connectorType === 'SINK' ? 'SinkRecordReadRate' : 'SourceRecordPollRate'),
|
||||
},
|
||||
{
|
||||
title: '消息写入速率(KB/s)',
|
||||
dataIndex: 'writeRate',
|
||||
key: 'writeRate',
|
||||
sorter: true,
|
||||
width: 170,
|
||||
render: (value: any, record: any) =>
|
||||
renderLine(record, record.connectorType === 'SINK' ? 'SinkRecordSendRate' : 'SourceRecordWriteRate'),
|
||||
},
|
||||
{
|
||||
title: '消息处理错误次数(次)',
|
||||
dataIndex: 'recordErrors',
|
||||
key: 'recordErrors',
|
||||
sorter: true,
|
||||
width: 170,
|
||||
render: (value: any, record: any) => renderLine(record, 'TotalRecordErrors'),
|
||||
},
|
||||
{
|
||||
title: 'Topics',
|
||||
dataIndex: 'topicNameList',
|
||||
key: 'topicNameList',
|
||||
width: 200,
|
||||
render(t: any, r: any) {
|
||||
return t && t.length > 0 ? <TagsWithHide placement="bottom" list={t} expandTagContent={(num: any) => `共有${num}个`} /> : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'options',
|
||||
key: 'options',
|
||||
width: 200,
|
||||
filterTitle: true,
|
||||
fixed: 'right',
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: (_t: any, r: any) => {
|
||||
return (
|
||||
<div>
|
||||
<Popconfirm
|
||||
title="是否重启当前任务?"
|
||||
onConfirm={() => arg?.optionConnect(r, 'restart')}
|
||||
// onCancel={cancel}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
overlayClassName="connect-popconfirm"
|
||||
>
|
||||
<Button key="restart" type="link" size="small">
|
||||
重启
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
|
||||
{(r.state === 'RUNNING' || r.state === 'PAUSED') && (
|
||||
<Popconfirm
|
||||
title={`是否${r.state === 'RUNNING' ? '暂停' : '继续'}当前任务?`}
|
||||
onConfirm={() => arg?.optionConnect(r, r.state === 'RUNNING' ? 'stop' : 'resume')}
|
||||
// onCancel={cancel}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
overlayClassName="connect-popconfirm"
|
||||
>
|
||||
<Button key="stopResume" type="link" size="small">
|
||||
{/* {r?.state !== 1 ? '继续' : '暂停'} */}
|
||||
{r.state === 'RUNNING' ? '暂停' : '继续'}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
|
||||
<Button type="link" size="small" onClick={() => arg?.editConnector(r)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Delete record={r} onConfirm={arg?.deleteTesk}></Delete>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
|
||||
// Workers
|
||||
export const getWorkersColumns = (arg?: any) => {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Worker Host',
|
||||
dataIndex: 'workerHost',
|
||||
key: 'workerHost',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '所属集群',
|
||||
dataIndex: 'connectClusterName',
|
||||
key: 'connectClusterName',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Connectors',
|
||||
dataIndex: 'connectorCount',
|
||||
key: 'connectorCount',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Tasks',
|
||||
dataIndex: 'taskCount',
|
||||
key: 'taskCount',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
|
||||
// Detail
|
||||
export const getConnectorsDetailColumns = (arg?: any) => {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Task ID',
|
||||
dataIndex: 'taskId',
|
||||
key: 'taskId',
|
||||
width: 240,
|
||||
render: (t: any, r: any) => {
|
||||
return (
|
||||
<span>
|
||||
{t}
|
||||
{
|
||||
<Tag
|
||||
style={{
|
||||
background: stateEnum[r?.state]?.bgColor,
|
||||
color: stateEnum[r?.state]?.color,
|
||||
padding: '3px 6px',
|
||||
marginLeft: '5px',
|
||||
}}
|
||||
>
|
||||
{Utils.firstCharUppercase(r?.state as string)}
|
||||
</Tag>
|
||||
}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Worker',
|
||||
dataIndex: 'workerId',
|
||||
key: 'workerId',
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
title: '错误原因',
|
||||
dataIndex: 'trace',
|
||||
key: 'trace',
|
||||
width: 400,
|
||||
needTooltip: true,
|
||||
lineClampOne: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'role',
|
||||
key: 'role',
|
||||
width: 100,
|
||||
render: (_t: any, r: any) => {
|
||||
return (
|
||||
<div>
|
||||
<Popconfirm
|
||||
title="是否重试当前任务?"
|
||||
onConfirm={() => arg?.retryOption(r.taskId)}
|
||||
// onCancel={cancel}
|
||||
okText="是"
|
||||
cancelText="否"
|
||||
overlayClassName="connect-popconfirm"
|
||||
>
|
||||
<a>重试</a>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
@@ -0,0 +1,193 @@
|
||||
// connect列表 图表
|
||||
.metric-data-wrap {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
width: 100%;
|
||||
.cur-val {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
.dcloud-spin-nested-loading {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增按钮
|
||||
.add-connect {
|
||||
.dcloud-btn-primary:hover,
|
||||
.dcloud-btn-primary:focus {
|
||||
// 可以控制新增按钮的hover和focus的样式
|
||||
// background: #556ee6;
|
||||
// border-color: #556ee6;
|
||||
}
|
||||
&-btn {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
border-right: none;
|
||||
}
|
||||
&-dropdown-menu {
|
||||
.dcloud-dropdown-menu {
|
||||
border-radius: 8px;
|
||||
&-item {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-json {
|
||||
padding: 8px 12px !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
// connect详情
|
||||
.connect-detail-drawer {
|
||||
.card-bar-container {
|
||||
background: rgba(86, 110, 230, 0.04) !important;
|
||||
|
||||
.card-bar-colunms {
|
||||
background-color: rgba(86, 110, 230, 0);
|
||||
}
|
||||
}
|
||||
&-title {
|
||||
margin: 20px 0 8px;
|
||||
font-family: @font-family-bold;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
.del-connect-modal {
|
||||
.tip-info {
|
||||
display: flex;
|
||||
color: #592d00;
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
background: #fffae0;
|
||||
border-radius: 4px;
|
||||
.anticon {
|
||||
color: #ffc300;
|
||||
margin-right: 4px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.test-right-away {
|
||||
color: #556ee6;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dcloud-alert-content {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重启、继续/暂停 气泡卡片
|
||||
.connect-popconfirm {
|
||||
.dcloud-popover-inner-content {
|
||||
padding: 6px 16px;
|
||||
}
|
||||
.dcloud-popover-inner {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.dcloud-popover-message,
|
||||
.dcloud-btn {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.operate-connector-drawer {
|
||||
.connector-plugin-desc {
|
||||
font-size: 13px;
|
||||
.connector-plugin-title {
|
||||
font-family: @font-family-bold;
|
||||
}
|
||||
}
|
||||
.dcloud-collapse.add-connector-collapse {
|
||||
.add-connector-collapse-panel,
|
||||
.add-connector-collapse-panel:last-child {
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
background: #f8f9fa;
|
||||
border: 0px;
|
||||
border-radius: 8px;
|
||||
.dcloud-collapse-header {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
.dcloud-collapse-arrow {
|
||||
margin-right: 8px !important;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
&:hover .dcloud-collapse-extra {
|
||||
opacity: 1;
|
||||
}
|
||||
&:not(.dcloud-collapse-item-active) {
|
||||
.dcloud-collapse-header:hover {
|
||||
background: #f1f3ff;
|
||||
}
|
||||
}
|
||||
.dcloud-collapse-content-box {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
padding: 20px 14px 0 14px;
|
||||
.dcloud-form-item {
|
||||
flex: 1 0 50%;
|
||||
.dcloud-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
&:nth-child(2n) {
|
||||
padding-left: 6px;
|
||||
}
|
||||
&:nth-child(2n + 1) {
|
||||
padding-right: 6px;
|
||||
}
|
||||
.dcloud-form-item-control-input {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-container-plugin-select {
|
||||
height: 27px;
|
||||
margin: 4px 0;
|
||||
position: relative;
|
||||
.dcloud-form-item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.dcloud-form-item-control-input {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dcloud-alert {
|
||||
margin: 16px 0 24px 0;
|
||||
padding: 0 12px;
|
||||
border: unset;
|
||||
border-radius: 8px;
|
||||
background: #fffae0;
|
||||
.dcloud-alert-message {
|
||||
font-size: 13px;
|
||||
color: #592d00;
|
||||
.dcloud-btn {
|
||||
padding: 0 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.operate-connector-drawer-use-json {
|
||||
.CodeMirror.cm-s-default {
|
||||
height: calc(100vh - 146px);
|
||||
}
|
||||
.dcloud-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { ProTable, Dropdown, Button, Utils, AppContainer, SearchInput, Menu } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import API from '../../api';
|
||||
import { getConnectorsColumns, defaultPagination, optionType } from './config';
|
||||
import { tableHeaderPrefix } from '@src/constants/common';
|
||||
import ConnectCard from '@src/components/CardBar/ConnectCard';
|
||||
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
|
||||
import AddConnector, { OperateInfo } from './AddConnector';
|
||||
import ConnectorDetail from './Detail';
|
||||
import notification from '@src/components/Notification';
|
||||
import './index.less';
|
||||
import AddConnectorUseJSON from './AddConnectorUseJSON';
|
||||
import HasConnector from './HasConnector';
|
||||
const { request } = Utils;
|
||||
|
||||
const rateMap: any = {
|
||||
readRate: ['SinkRecordReadRate', 'SourceRecordPollRate'],
|
||||
writeRate: ['SinkRecordSendRate', 'SourceRecordWriteRate'],
|
||||
recordErrors: ['TotalRecordErrors'],
|
||||
};
|
||||
|
||||
const Connectors: React.FC = () => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [detailVisible, setDetailVisible] = useState(false);
|
||||
const [data, setData] = useState([]);
|
||||
const [searchKeywords, setSearchKeywords] = useState('');
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
const [sortInfo, setSortInfo] = useState({});
|
||||
const [detailRecord, setDetailRecord] = useState('');
|
||||
const [healthType, setHealthType] = useState(true);
|
||||
const addConnectorRef = useRef(null);
|
||||
const addConnectorJsonRef = useRef(null);
|
||||
|
||||
const getRecent1DayTimeStamp = () => [Date.now() - 24 * 60 * 60 * 1000, Date.now()];
|
||||
// 请求接口获取数据
|
||||
const genData = async ({ pageNo, pageSize, filters, sorter }: any) => {
|
||||
const [startStamp, endStamp] = getRecent1DayTimeStamp();
|
||||
if (global?.clusterInfo?.id === undefined) return;
|
||||
setLoading(true);
|
||||
const params = {
|
||||
metricLines: {
|
||||
aggType: 'avg',
|
||||
endTime: endStamp,
|
||||
metricsNames: ['SourceRecordPollRate', 'SourceRecordWriteRate', 'SinkRecordReadRate', 'SinkRecordSendRate', 'TotalRecordErrors'],
|
||||
startTime: startStamp,
|
||||
topNu: 0,
|
||||
},
|
||||
searchKeywords: searchKeywords.slice(0, 128),
|
||||
pageNo,
|
||||
pageSize,
|
||||
latestMetricNames: ['SourceRecordPollRate', 'SourceRecordWriteRate', 'SinkRecordReadRate', 'SinkRecordSendRate', 'TotalRecordErrors'],
|
||||
sortType: sorter?.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : 'desc',
|
||||
sortMetricNameList: rateMap[sorter?.field] || [],
|
||||
};
|
||||
|
||||
request(API.getConnectorsList(global?.clusterInfo?.id), { method: 'POST', data: params })
|
||||
.then((res: any) => {
|
||||
setPagination({
|
||||
current: res.pagination?.pageNo,
|
||||
pageSize: res.pagination?.pageSize,
|
||||
total: res.pagination?.total,
|
||||
});
|
||||
const newData =
|
||||
res?.bizData.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
...item?.latestMetrics?.metrics,
|
||||
key: item.connectClusterName + item.connectorName,
|
||||
};
|
||||
}) || [];
|
||||
setData(newData);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||
setSortInfo(sorter);
|
||||
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter });
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<Menu className="">
|
||||
<Menu.Item>
|
||||
<span onClick={() => addConnectorJsonRef.current?.onOpen('create')}>JSON 新增Connector</span>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const getDetailInfo = (record: any) => {
|
||||
setDetailRecord(record);
|
||||
setDetailVisible(true);
|
||||
};
|
||||
|
||||
// 编辑
|
||||
const editConnector = (detail: OperateInfo['detail']) => {
|
||||
addConnectorRef.current?.onOpen('edit', addConnectorJsonRef.current, detail);
|
||||
};
|
||||
|
||||
// 重启、暂停/继续 操作
|
||||
const optionConnect = (record: any, action: string) => {
|
||||
const params = {
|
||||
action,
|
||||
connectClusterId: record?.connectClusterId,
|
||||
connectorName: record?.connectorName,
|
||||
};
|
||||
|
||||
request(API.connectorsOperates, { method: 'PUT', data: params }).then((res: any) => {
|
||||
if (res === null) {
|
||||
notification.success({
|
||||
message: `任务已${optionType[params.action]}`,
|
||||
description: `任务状态更新会有至多1min延迟`,
|
||||
});
|
||||
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo });
|
||||
setHealthType(!healthType);
|
||||
} else {
|
||||
notification.error({
|
||||
message: `${optionType[params.action]}任务失败`,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除任务
|
||||
const deleteTesk = () => {
|
||||
genData({ pageNo: 1, pageSize: pagination.pageSize });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
genData({
|
||||
pageNo: 1,
|
||||
pageSize: pagination.pageSize,
|
||||
sorter: sortInfo,
|
||||
});
|
||||
}, [searchKeywords]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
|
||||
<DBreadcrumb
|
||||
breadcrumbs={[
|
||||
{ label: '多集群管理', aHref: '/' },
|
||||
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
|
||||
{ label: 'Connect', aHref: `/cluster/${global?.clusterInfo?.id}/connect` },
|
||||
{ label: 'Connectors', aHref: `` },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<HasConnector>
|
||||
<>
|
||||
<div style={{ margin: '12px 0' }}>
|
||||
<ConnectCard state={healthType} />
|
||||
</div>
|
||||
<div className="custom-table-content">
|
||||
<div className={tableHeaderPrefix}>
|
||||
<div className={`${tableHeaderPrefix}-left`}>
|
||||
<div
|
||||
className={`${tableHeaderPrefix}-left-refresh`}
|
||||
onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
|
||||
>
|
||||
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${tableHeaderPrefix}-right`}>
|
||||
<SearchInput
|
||||
onSearch={setSearchKeywords}
|
||||
attrs={{
|
||||
placeholder: '请输入Connector',
|
||||
style: { width: '248px', borderRiadus: '8px' },
|
||||
maxLength: 128,
|
||||
}}
|
||||
/>
|
||||
<span className="add-connect">
|
||||
<Button
|
||||
className="add-connect-btn"
|
||||
icon={<IconFont type="icon-jiahao" />}
|
||||
type="primary"
|
||||
onClick={() => addConnectorRef.current?.onOpen('create', addConnectorJsonRef.current)}
|
||||
>
|
||||
新增Connector
|
||||
</Button>
|
||||
<Dropdown overlayClassName="add-connect-dropdown-menu" overlay={menu}>
|
||||
<Button className="add-connect-json" type="primary">
|
||||
<IconFont type="icon-guanwangxiala" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ProTable
|
||||
key="connector-table"
|
||||
showQueryForm={false}
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
rowKey: 'key',
|
||||
loading: loading,
|
||||
columns: getConnectorsColumns({ getDetailInfo, deleteTesk, optionConnect, editConnector }),
|
||||
dataSource: data,
|
||||
paginationProps: { ...pagination },
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
scroll: { x: 'max-content', y: 'calc(100vh - 400px)' },
|
||||
bordered: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</HasConnector>
|
||||
|
||||
<ConnectorDetail visible={detailVisible} setVisible={setDetailVisible} record={detailRecord} />
|
||||
<AddConnector
|
||||
ref={addConnectorRef}
|
||||
refresh={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo })}
|
||||
/>
|
||||
<AddConnectorUseJSON
|
||||
ref={addConnectorJsonRef}
|
||||
refresh={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize, sorter: sortInfo })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Connectors;
|
||||
Reference in New Issue
Block a user