初始化3.0.0版本

This commit is contained in:
zengqiao
2022-08-18 17:04:05 +08:00
parent 462303fca0
commit 51832385b1
2446 changed files with 93177 additions and 127211 deletions

View File

@@ -0,0 +1,245 @@
import { AppContainer, Form, message, Tabs, Utils } from 'knowdesign';
import * as React from 'react';
import ConfigForm from './component/ConfigFrom';
import TestResult from '../TestingConsumer/component/Result';
import API from '../../api';
import { getFormConfig, getTableColumns, tabsConfig } from './config';
import './index.less';
import { useParams } from 'react-router-dom';
const { TabPane } = Tabs;
const ProduceClientTest = () => {
const [global] = AppContainer.useGlobalValue();
const [form] = Form.useForm();
const customFormRef: any = React.createRef();
const [configInfo, setConfigInfo] = React.useState({});
const [activeKey, setActiveKey] = React.useState(tabsConfig[0].name);
const [tableData, setTableData] = React.useState([]);
const [topicMetaData, setTopicMetaData] = React.useState([]);
const [running, setRunning] = React.useState(false);
const [isKeyOn, setIsKeyOn] = React.useState(true);
const { clusterId } = useParams<{ clusterId: string }>();
const currentInterval = React.useRef(null);
let currentMsgNum = 0;
let startTime = 0;
React.useEffect(() => {
Utils.request(API.getTopicMetaData(+clusterId))
.then((res: any) => {
const filterRes = res.filter((item: any) => item.type !== 1);
const topics = (filterRes || []).map((item: any) => {
return {
label: item.topicName,
value: item.topicName,
partitionIdList: item.partitionIdList,
};
});
setTopicMetaData(topics);
})
.catch((err) => {
message.error(err);
});
return () => {
clearInterval();
};
}, []);
const runProduceClient = (values: any, isFirst = false) => {
// 记录每一次请求发送的条数
currentMsgNum = currentMsgNum ? currentMsgNum + values.chunks : values.chunks;
const params = {
clientProperties: {
acks: values.acks,
'compression.type': values.compressionType,
},
clusterId,
partitionIdList: values.frocePartition,
recordCount: values.chunks,
recordHeader: values.recordHeader,
recordKey: isKeyOn ? values.key || '' : undefined,
recordOperate: isFirst,
recordValue: values.value,
topicName: values.topicName,
};
Utils.post(API.postClientProducer, params)
.then((res: any) => {
if (isFirst) {
startTime = new Date().getTime();
}
setTableData(res || []);
if (values.producerMode === 'timed') {
if (values.interval && !currentInterval.current) {
// const randomValue = Math.random() < 0.5 ? -1 : 1;
// const random = +values.jitter ? +values.interval + randomValue * (Math.random() * +values.jitter) : values.interval;
const random = values.interval;
currentInterval.current = window.setInterval(() => {
runProduceClient(values);
}, random);
}
// 第一次接口返回到现在的时间大于运行时间 当前发送条数大于总条数停止请求
const currentTime = new Date().getTime();
// if (currentTime - startTime >= +values.elapsed || currentMsgNum >= values.messageProduced) {
if (currentTime - startTime >= +values.elapsed) {
clearInterval();
setRunning(false);
}
} else {
// manual 方式
setRunning(false);
}
})
.catch((err) => {
// manual 方式
if (values.producerMode !== 'timed') {
setRunning(false);
}
});
};
const run = async () => {
form
.validateFields()
.then((values) => {
values.elapsed = values.elapsed * 60 * 1000;
const data = customFormRef.current.getTableData();
values.recordHeader = {};
for (const item of data) {
values.recordHeader[item.key] = item.value;
}
// 点击按钮重新定时器清空
clearInterval();
setRunning(true);
runProduceClient(values, true);
})
.catch((error) => {
const { values } = error;
if (!values.chunks || (values.producerMode === 'timed' && (!values.interval || !values.elapsed))) {
setActiveKey('Flow');
}
if (!values.topicName || !values.value) {
setActiveKey('Data');
}
});
};
const clearInterval = () => {
currentInterval.current && window.clearInterval(currentInterval.current);
currentInterval.current = null;
};
const stop = () => {
currentInterval.current && window.clearInterval(currentInterval.current);
setRunning(false);
};
const onHandleValuesChange = (value: any, allValues: any) => {
Object.keys(value).forEach((key) => {
let changeValue: any = null;
let partitionIdList = [];
switch (key) {
case 'producerMode':
changeValue = value[key];
setConfigInfo({
...configInfo,
needTimeOption: changeValue === 'timed',
});
break;
case 'interval':
changeValue = value[key];
setConfigInfo({
...configInfo,
maxJitter: +value[key] - 1,
});
break;
case 'topicName':
changeValue = value[key] as string;
AppContainer.eventBus.emit('ProduceTopicChange', value[key]);
partitionIdList = topicMetaData.find((item) => item.label === changeValue)?.partitionIdList || [];
partitionIdList = partitionIdList.map((item: number) => ({
label: item,
value: item,
}));
setConfigInfo({
...configInfo,
partitionIdList,
});
break;
}
});
};
const clearForm = () => {
setConfigInfo({});
clearInterval();
setTableData([]);
form.resetFields();
AppContainer.eventBus.emit('ProduceTopicChange', '');
setIsKeyOn(true);
customFormRef.current.resetTable();
};
const onTabChange = (key: string) => {
setActiveKey(key);
};
const onKeySwitchChange = (checked: boolean) => {
setIsKeyOn(checked);
};
const TabsContent = (
<div className="form-tabs">
<Tabs defaultActiveKey={tabsConfig[0].name} activeKey={activeKey} onChange={onTabChange}>
{tabsConfig.map(({ name, control }) =>
!control || (global.isShowControl && global.isShowControl(control)) ? <TabPane tab={name} key={name} /> : <></>
)}
</Tabs>
</div>
);
return (
<>
<div className="client-test-panel">
<ConfigForm
title="生产配置"
customContent={TabsContent}
customFormRef={customFormRef}
activeKey={activeKey}
formConfig={getFormConfig({
topicMetaData,
activeKey,
configInfo,
form,
onKeySwitchChange,
isKeyOn,
isShowControl: global.isShowControl,
})}
formData={{}}
form={form}
onHandleValuesChange={onHandleValuesChange}
clearForm={clearForm}
submit={run}
running={running}
stop={stop}
/>
<TestResult
showProcessList={false}
tableProps={{
scroll: { y: 600 },
columns: getTableColumns(),
dataSource: tableData,
pagination: false,
}}
showCardList={false}
/>
</div>
</>
);
};
export default ProduceClientTest;

View File

@@ -0,0 +1,124 @@
import { Button, Col, Form, Row } from 'knowdesign';
import { FormItemType, handleFormItem, IFormItem, renderFormItem } from 'knowdesign/lib/extend/x-form';
import * as React from 'react';
import './style/form.less';
import EditTable from './EditTable';
import { useIntl } from 'react-intl';
const prefixTesting = 'config-form-panel';
interface IProps {
formConfig: IFormItem[];
formData: any;
form: any;
customFormRef: any;
onHandleValuesChange?: any;
title: string;
customContent?: React.ReactNode;
customForm?: React.ReactNode;
clearForm: any;
submit: any;
stop: any;
activeKey: string;
running?: boolean;
}
export const renderFormContent = ({ formMap, formData, layout, formLayout, formItemColSpan = 24 }: any) => {
return (
<Row gutter={10}>
{formMap.map((formItem: IFormItem) => {
const { initialValue = undefined, valuePropName } = handleFormItem(formItem, formData);
if (formItem.type === FormItemType.text)
return (
<Col style={{ display: formItem.invisible ? 'none' : '' }} key={formItem.key} span={formItem.colSpan || formItemColSpan}>
{layout === 'vertical' ? (
<>
<span className="dcloud-form-item-label" style={{ padding: '0 0 7px', display: 'block' }}>
{formItem.label}
</span>
{(formItem as any).customFormItem ? (
<span style={{ fontSize: '14px', padding: '0 0 16px', display: 'block' }}>{(formItem as any).customFormItem}</span>
) : null}
</>
) : (
<Row style={{ padding: '6px 0 10px' }}>
<Col span={formLayout?.labelCol.span || 4} style={{ textAlign: 'right' }}>
<span className="dcloud-form-item-label" style={{ padding: '0 10px 0 0', display: 'inline-block' }}>
{formItem.label}:
</span>
</Col>
<Col span={formLayout?.wrapperCol.span || 20}>
<span>{(formItem as any).customFormItem}</span>
</Col>
</Row>
)}
</Col>
);
return (
<Col style={{ display: formItem.invisible ? 'none' : '' }} key={formItem.key} span={formItem.colSpan || formItemColSpan}>
<Form.Item
name={formItem.key}
key={formItem.key}
label={formItem.label}
rules={formItem.rules || [{ required: false, message: '' }]}
initialValue={initialValue}
valuePropName={valuePropName}
{...formItem.formAttrs}
>
{renderFormItem(formItem)}
</Form.Item>
</Col>
);
})}
</Row>
);
};
const ConfigForm = (props: IProps): JSX.Element => {
const {
formConfig,
activeKey,
customFormRef,
form,
formData,
onHandleValuesChange,
title,
clearForm,
submit,
stop,
running,
customContent,
} = props;
const intl = useIntl();
const onSubmit = () => {
running ? stop() : submit();
};
return (
<>
<div className={prefixTesting}>
<div className={`${prefixTesting}-title`}>{title}</div>
<div>{customContent}</div>
<div className={`${prefixTesting}-content`}>
<div style={{ display: activeKey === 'Header' ? '' : 'none' }}>
<EditTable ref={customFormRef} />
</div>
<Form form={form} layout={'vertical'} onValuesChange={onHandleValuesChange}>
{renderFormContent({ formMap: formConfig, formData, layout: 'vertical' })}
</Form>
</div>
<div className={`${prefixTesting}-footer-btn`}>
<Button loading={running} onClick={clearForm}>
{intl.formatMessage({ id: 'test.client.clear' })}
</Button>
<Button onClick={onSubmit} type="primary">
{intl.formatMessage({ id: running ? 'test.client.stop' : 'test.client.run' })}
</Button>
</div>
</div>
</>
);
};
export default ConfigForm;

View File

@@ -0,0 +1,40 @@
import { PicLeftOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { Button, Input, Tooltip } from 'knowdesign';
import * as React from 'react';
interface IProps {
value?: string;
onChange?: any;
tooltip?: string;
}
const CustomTextArea = (props: IProps) => {
const { tooltip, value, onChange } = props;
const genRandomContent = () => {
onChange && onChange(value.trim());
};
const onInputChange = ({ target: { value } }: { target: { value: string } }) => {
onChange && onChange(value.trim());
};
return (
<>
<div>
<span>
<Tooltip title={tooltip}>
<QuestionCircleOutlined />
</Tooltip>
</span>
<span>
<Tooltip title={'生成随机内容'}>
<PicLeftOutlined onClick={genRandomContent} />
</Tooltip>
</span>
</div>
<Input.TextArea value={value} onChange={onInputChange} />
</>
);
};
export default CustomTextArea;

View File

@@ -0,0 +1,217 @@
/* eslint-disable react/display-name */
import React, { useState } from 'react';
import { Table, Input, InputNumber, Popconfirm, Form, Typography, Button, message, IconFont } from 'knowdesign';
import './style/edit-table.less';
import { CheckOutlined, CloseOutlined, PlusSquareOutlined } from '@ant-design/icons';
const EditableCell = ({ editing, dataIndex, title, inputType, placeholder, record, index, children, ...restProps }: any) => {
const inputNode =
inputType === 'number' ? (
<InputNumber style={{ width: '130px' }} autoComplete="off" placeholder={placeholder} />
) : (
<Input autoComplete="off" placeholder={placeholder} />
);
return (
<td {...restProps}>
{editing ? (
<Form.Item
name={dataIndex}
style={{
margin: 0,
}}
rules={[
{
required: true,
message: `请输入!`,
},
]}
>
{inputNode}
</Form.Item>
) : (
children
)}
</td>
);
};
const EditTable = React.forwardRef((props: any, ref: any) => {
const { colCustomConfigs } = props;
const [form] = Form.useForm();
const [data, setData] = useState([]);
const [editingKey, setEditingKey] = useState(0);
const isEditing = (record: any) => record.editKey === editingKey;
const getTableData = () => {
return data;
};
const resetTable = () => {
setData([]);
};
React.useImperativeHandle(ref, () => ({
getTableData,
resetTable,
}));
const edit = (record: any) => {
form.setFieldsValue({
key: '',
value: '',
...record,
});
setEditingKey(record.editKey);
};
const deleteRow = (record: any) => {
const _data = data.filter((item) => item.editKey !== record.editKey);
setData(_data);
};
const cancel = (record: any) => {
if (record.key === '') {
const _data = data.filter((item) => item.editKey !== record.editKey);
setData(_data);
}
setEditingKey(0);
};
const add = () => {
if (editingKey !== 0) {
message.error('请先保存当前编辑项');
return;
}
form.resetFields();
const editKey = data.length ? data[data.length - 1].editKey + 1 : 1;
setData([...data, { key: '', value: '', editKey }]);
setEditingKey(editKey);
};
const save = async (editKey: number) => {
try {
const row = await form.validateFields();
const newData = [...data];
const index = newData.findIndex((item) => editKey === item.editKey);
if (index > -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
setData(newData);
setEditingKey(0);
} else {
newData.push(row);
setData(newData);
setEditingKey(0);
}
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
};
const columns = [
{
title: 'key',
dataIndex: 'key',
width: 40,
editable: true,
},
{
title: 'value',
dataIndex: 'value',
width: 40,
editable: true,
},
{
title: '',
dataIndex: 'operation',
width: 20,
className: 'no-padding',
render: (text: any, record: any) => {
const editable = isEditing(record);
return editable ? (
<span>
<Typography.Link
onClick={() => save(record.editKey)}
style={{
marginRight: 8,
}}
>
<CheckOutlined />
</Typography.Link>
{/* <Popconfirm title="确认取消?" onConfirm={() => cancel(record)}> */}
<a>
<CloseOutlined onClick={() => cancel(record)} />
</a>
{/* </Popconfirm> */}
</span>
) : (
<>
<Typography.Link
className="custom-typography"
disabled={editingKey !== 0}
style={{
marginRight: 8,
}}
onClick={() => edit(record)}
>
<IconFont type={'icon-bianji1'} size={11} />
</Typography.Link>
<Typography.Link className="custom-typography" disabled={editingKey !== 0} onClick={() => deleteRow(record)}>
<IconFont type={'icon-shanchu1'} size={11} />
</Typography.Link>
</>
);
},
},
];
const mergedColumns = columns.map((col, index) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: any) =>
Object.assign(
{
record,
inputType: 'text',
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record),
},
colCustomConfigs?.[index]
),
title: colCustomConfigs?.[index]?.title || col.title,
};
});
return (
<div className="edit-table-form">
<Form form={form} component={false}>
<Table
components={{
body: {
cell: EditableCell,
},
}}
// scroll={{
// x: true,
// }}
dataSource={data}
columns={mergedColumns}
rowClassName="editable-row"
pagination={false}
/>
</Form>
<div className="add-btn">
<Button type="link" onClick={add}>
<PlusSquareOutlined />
</Button>
</div>
</div>
);
});
export default EditTable;

View File

@@ -0,0 +1,42 @@
.edit-table-form {
.dcloud-table-thead>tr>th:not(:last-child):not(.dcloud-table-selection-column):not(.dcloud-table-row-expand-icon-cell):not([colspan])::before {
width: 0px;
}
table {
table-layout: fixed !important;
}
.dcloud-table .dcloud-table-content .dcloud-table-cell {
padding: 4px 12px;
height: 36px;
font-family: HelveticaNeue-Bold;
font-size: 13px;
color: #495057;
letter-spacing: 0;
.dcloud-form-item-control-input {
min-height: 27px;
}
.dcloud-input {
padding: 4px 12px;
height: 27px;
line-height: 27px;
background: rgba(33, 37, 41, 0.04);
border-radius: 6px;
}
.custom-typography {
color: #74788D;
}
}
.add-btn {
height: 36px;
line-height: 36px;
border-bottom: 1px solid #EFF2F7;
}
.no-padding {
padding: 0px !important;
}
}

View File

@@ -0,0 +1,57 @@
.config-form-panel {
width: 280px;
min-width: 280px;
min-height: 260px;
margin-right: 12px;
padding: 16px 24px 60px;
background: #FFFFFF;
box-shadow: 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);
border-radius: 12px 12px 0 0;
position: relative;
height: calc(100vh - 156px);
.dcloud-radio-wrapper.dcloud-radio-wrapper-checked {
.dcloud-radio-inner {
background-color: #526ecc;
}
}
.dcloud-radio-inner {
border-color: #ADB5BD;
}
.dcloud-radio-inner::after {
background-color: #fff;
}
&-title {
font-family: @font-family-bold;
font-size: 18px;
color: #212529;
letter-spacing: 0;
text-align: justify;
line-height: 20px;
padding-bottom: 16px;
}
&-content{
overflow-y: auto;
height: calc(100vh - 326px);
min-height: 90px;
}
&-footer-btn {
position: absolute;
bottom: 10px;
height: 48px;
line-height: 48px;
width: 240px;
text-align: right;
border-top: 1px solid #EBEDEF;
padding-top: 5px;
button+button {
margin-left: 8px;
}
}
}

View File

@@ -0,0 +1,396 @@
import { QuestionCircleOutlined } from '@ant-design/icons';
import { IconFont, Switch, Tooltip } from 'knowdesign';
import { FormItemType, IFormItem } from 'knowdesign/lib/extend/x-form';
import moment from 'moment';
import React from 'react';
import { timeFormat } from '../../constants/common';
import { getRandomStr } from '../../lib/utils';
import { ControlStatusMap } from '../CommonRoute';
export const filterList = [
{
label: 'none',
value: 'none',
},
{
label: 'contains',
value: 'contains',
},
{
label: 'does not contains',
value: 'does not contains',
},
{
label: 'equals',
value: 'equals',
},
{
label: 'Above Size',
value: 'aboveSize',
},
{
label: 'Under Size',
value: 'underSize',
},
];
export const untilList = [
{
label: 'Forever',
value: 'forever',
},
{
label: 'number of messages',
value: 'number of messages',
},
{
label: 'number of messages per partition',
value: 'number of messages per partition',
},
{
label: 'max size',
value: 'max size',
},
{
label: 'max size per partition',
value: 'max size per partition',
},
{
label: 'timestamp',
value: 'timestamp',
},
];
export const tabsConfig = [
{ name: 'Data', control: '' },
{ name: 'Flow', control: '' },
{ name: 'Header', control: ControlStatusMap.TESTING_PRODUCER_HEADER },
{ name: 'Options', control: '' },
];
const getComplexLabel = (key: string, label: string, form: any, onSwitchChange?: any, needSwitch = false) => (
<div className="complex-label">
<span>
<span>{label}</span>
<Tooltip title="暂支持string格式">
<QuestionCircleOutlined size={12} style={{ marginLeft: 2 }} />
</Tooltip>
:
{needSwitch ? (
<span>
<Switch onClick={onSwitchChange} size="small" defaultChecked className="switch" />
</span>
) : null}
</span>
<span>
<Tooltip title={'生成随机内容'}>
<IconFont
type="icon-shengchengdaima"
className="random-icon"
onClick={() => {
const randomStr = getRandomStr(key === 'key' ? 30 : 128);
form && form.setFieldsValue({ [key]: randomStr });
}}
/>
</Tooltip>
</span>
</div>
);
export const getFormConfig = (params: any) => {
const { topicMetaData, activeKey: type, configInfo: info, form, onKeySwitchChange, isKeyOn, isShowControl } = params;
const formConfig = [
{
key: 'topicName',
label: 'Topic',
type: FormItemType.select,
invisible: type !== 'Data',
rules: [{ required: true, message: '请选择Topic' }],
options: topicMetaData,
attrs: {
showSearch: true,
},
},
{
key: 'key',
label: getComplexLabel('key', 'Key', form, onKeySwitchChange, true),
type: FormItemType.textArea,
invisible: type !== 'Data',
rules: [
{
required: false,
message: '请输入Key',
},
],
attrs: {
disabled: !isKeyOn,
rows: 5,
placeholder: '暂支持string类型',
},
},
{
key: 'value',
label: getComplexLabel('value', 'Value', form),
type: FormItemType.textArea,
invisible: type !== 'Data',
rules: [
{
required: true,
message: '请输入Value',
},
],
attrs: {
rows: 5,
placeholder: '暂支持string类型',
},
},
{
key: 'chunks',
label: '单次发送消息数',
type: FormItemType.inputNumber,
invisible: type !== 'Flow',
defaultValue: 1,
rules: [{ required: true, message: '请输入' }],
attrs: {
min: 0,
style: { width: 232 },
},
},
{
key: 'producerMode',
label: '生产模式',
type: FormItemType.radioGroup,
invisible: type !== 'Flow',
defaultValue: 'manual',
options: [
{
label: '手动',
value: 'manual',
},
{
label: '周期',
value: 'timed',
},
],
rules: [
{
required: true,
message: '请选择生产模式',
},
],
},
// {
// key: 'timeOptions',
// label: 'Timer options',
// type: FormItemType.text,
// invisible: type !== 'Flow' || !info?.needTimeOption,
// customFormItem: null,
// rules: [
// {
// required: false,
// message: '请选择Producer Mode',
// },
// ],
// },
{
key: 'elapsed',
label: '运行总时间min',
type: FormItemType.inputNumber,
invisible: type !== 'Flow' || !info?.needTimeOption,
rules: [
{
required: info?.needTimeOption,
message: '请输入',
},
],
attrs: {
min: 0,
max: 300,
style: { width: '100%' },
},
},
{
key: 'interval',
label: '时间间隔ms',
type: FormItemType.inputNumber,
invisible: type !== 'Flow' || !info?.needTimeOption,
rules: [
{
required: info?.needTimeOption,
message: '请输入',
},
],
attrs: {
min: 300, // 300ms间隔保证请求不太频繁
style: { width: '100%' },
},
},
// {
// key: 'jitter',
// label: 'Max jitterms',
// type: FormItemType.inputNumber,
// invisible: type !== 'Flow' || !info?.needTimeOption,
// rules: [
// {
// required: info?.needTimeOption,
// message: '请输入Max jitter',
// },
// ],
// attrs: {
// max: info.maxJitter || 0,
// size: 'small',
// style: { width: 216 },
// },
// formAttrs: {
// className: 'inner-item',
// },
// },
// {
// key: 'lifecycle',
// label: (
// <>
// Lifecycle options
// <Tooltip title="Shutdown the producer automatically">
// <QuestionCircleOutlined style={{ marginLeft: 8 }} />
// </Tooltip>
// </>
// ),
// type: FormItemType.text,
// invisible: type !== 'Flow' || !info?.needTimeOption,
// customFormItem: null,
// rules: [
// {
// required: false,
// message: '',
// },
// ],
// },
// {
// key: 'messageProduced',
// label: 'Number of message produced',
// type: FormItemType.inputNumber,
// invisible: type !== 'Flow' || !info?.needTimeOption,
// rules: [
// {
// required: info?.needTimeOption,
// message: '请输入Number of message produced',
// },
// ],
// attrs: {
// min: 0,
// size: 'small',
// style: { width: 216 },
// },
// formAttrs: {
// className: 'inner-item',
// },
// },
{
key: 'frocePartition',
label: 'Froce Partition',
invisible: type !== 'Options',
type: FormItemType.select,
attrs: {
mode: 'multiple',
},
options: info.partitionIdList || [],
rules: [{ required: false, message: '请选择' }],
},
{
key: 'compressionType',
label: 'Compression Type',
type: FormItemType.radioGroup,
defaultValue: 'none',
invisible: type !== 'Options',
options: (() => {
const options = [
{
label: 'none',
value: 'none',
},
{
label: 'gzip',
value: 'gzip',
},
{
label: 'snappy',
value: 'snappy',
},
{
label: 'lz4',
value: 'lz4',
},
];
if (isShowControl && isShowControl(ControlStatusMap.TESTING_PRODUCER_COMPRESSION_TYPE_ZSTD)) {
options.push({
label: 'zstd',
value: 'zstd',
});
}
return options;
})(),
rules: [
{
required: false,
message: '请选择Compression Type',
},
],
},
{
key: 'acks',
label: 'Acks',
type: FormItemType.radioGroup,
defaultValue: '0',
invisible: type !== 'Options',
options: [
{
label: 'none',
value: '0',
},
{
label: 'leader',
value: '1',
},
{
label: 'all',
value: 'all',
},
],
rules: [
{
required: false,
message: '请选择Acks',
},
],
},
] as unknown as IFormItem[];
return formConfig;
};
export const getTableColumns = () => {
return [
{
title: 'Partition',
dataIndex: 'partitionId',
},
{
title: 'offset',
dataIndex: 'offset',
},
{
title: 'Timestamp',
dataIndex: 'timestampUnitMs',
render: (text: number) => {
return moment(text).format(timeFormat);
},
},
{
title: 'time',
dataIndex: 'costTimeUnitMs',
width: 60,
},
];
};

View File

@@ -0,0 +1,102 @@
.client-test-panel {
display: flex;
.form-tabs {
background: transparent;
.dcloud-tabs-nav {
height: 32px;
background: none;
line-height: 32px;
}
.dcloud-tabs-top>.dcloud-tabs-nav,
.dcloud-tabs-bottom>.dcloud-tabs-nav,
.dcloud-tabs-top>div>.dcloud-tabs-nav,
.dcloud-tabs-bottom>div>.dcloud-tabs-nav {
margin-bottom: 18px;
padding: 0px;
}
.dcloud-tabs-tab+.dcloud-tabs-tab {
margin: 0 0 0 16px;
}
.dcloud-tabs-top>.dcloud-tabs-nav::before,
.dcloud-tabs-bottom>.dcloud-tabs-nav::before,
.dcloud-tabs-top>div>.dcloud-tabs-nav::before,
.dcloud-tabs-bottom>div>.dcloud-tabs-nav::before {
border: none;
}
}
.dcloud-form-item {
margin-bottom: 14px;
&-label {
font-family: @font-family-bold;
font-size: 13px;
color: #495057;
letter-spacing: 0;
line-height: 18px !important;
.complex-label {
width: 228px;
display: flex;
justify-content: space-between;
.random-icon {
font-size: 18px;
height: 18px;
color: #ADB5BC;
}
.random-icon:hover {
height: 18px;
text-align: center;
border-radius: 9px;
line-height: 20px;
color: #74788D;
background: rgba(33, 37, 41, 0.04);
}
.switch {
margin-left: 12px;
}
}
}
.dcloud-form-item-control-input {
textarea {
height: 110px;
border-radius: 8px;
}
}
}
.dcloud-form-item.inner-item {
margin-left: 16px;
margin-bottom: 7px;
.dcloud-form-item-label {
padding: 0 0 4px;
>label {
font-size: 13px;
color: #74788D;
letter-spacing: 0;
}
}
}
.dcloud-radio-group{
display: flex;
flex-wrap: wrap;
.dcloud-radio-wrapper{
width: 33.33%;
margin: 0;
}
}
}

View File

@@ -0,0 +1,42 @@
import { AppContainer } from 'knowdesign';
import * as React from 'react';
import ProduceClientTest from './Produce';
import './index.less';
import TaskTabs from '../TestingConsumer/component/TaskTabs';
import DBreadcrumb from 'knowdesign/lib/extend/d-breadcrumb';
import { useParams } from 'react-router-dom';
const Produce = () => {
const initial = {
label: '生产',
key: 'tab-1',
closable: false,
tabpane: <ProduceClientTest />,
};
const { clusterId } = useParams<{ clusterId: string }>();
const [global] = AppContainer.useGlobalValue();
const ref: any = React.useRef();
React.useEffect(() => {
AppContainer.eventBus.on('ProduceTopicChange', (args: string) => {
ref.current && ref.current.setTabsTitle && ref.current.setTabsTitle(`生产 ${args}`);
});
}, []);
return (
<>
<div className="breadcrumb">
<DBreadcrumb
breadcrumbs={[
{ label: '多集群管理', aHref: '/' },
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
{ label: 'Produce', aHref: '' },
]}
/>
</div>
<TaskTabs initial={initial} ref={ref} />
</>
);
};
export default Produce;