kafka-manager 2.0

This commit is contained in:
zengqiao
2020-09-28 15:46:34 +08:00
parent 28d985aaf1
commit c6e4b60424
1253 changed files with 82183 additions and 37179 deletions

View File

@@ -0,0 +1,277 @@
import { notification } from 'component/antd';
import { wrapper } from 'store';
import { IClusterTopics, IEditTopic, IConfigInfo, ILogicalCluster, INewLogical, IMetaData, IBrokersRegions, INewRegions } from 'types/base-type';
import { editTopic } from 'lib/api';
import { transMSecondToHour, transHourToMSecond } from 'lib/utils';
import { cluster } from 'store/cluster';
import { admin } from 'store/admin';
import { app } from 'store/app';
export const showEditClusterTopic = (item: IClusterTopics) => {
const xFormModal = {
formMap: [
{
key: 'clusterName',
label: '集群名称',
rules: [{
required: true,
}],
attrs: {
disabled: true,
},
},
{
key: 'appId',
label: '应用ID',
rules: [{
required: true,
}],
attrs: {
disabled: true,
},
},
{
key: 'topicName',
label: 'Topic名称',
rules: [{
required: true,
}],
attrs: {
disabled: true,
},
},
{
key: 'retentionTime',
label: '保存时间',
rules: [{
required: true,
message: '请输入保存时间',
}],
attrs: {
placeholder: '请输入保存时间',
suffix: '小时',
},
},
{
key: 'properties',
label: 'Topic属性列表',
type: 'text_area',
rules: [{ required: false}],
attrs: {
placeholder: '请输入Topic属性列表',
},
},
{
key: 'description',
label: '备注',
type: 'text_area',
rules: [{
required: false,
}],
attrs: {
placeholder: '请输入备注',
},
},
],
formData: {
clusterName: item.clusterName,
appId: item.appId,
topicName: item.topicName,
retentionTime: transMSecondToHour(item.retentionTime),
properties: JSON.stringify(item.properties, null, 4),
description: item.description,
},
visible: true,
title: 'Topic编辑',
onSubmit: (value: IEditTopic) => {
value.clusterId = item.clusterId;
value.properties = value.properties ? JSON.parse(value.properties) : {};
value.retentionTime = transHourToMSecond(value.retentionTime);
editTopic(value).then(data => {
notification.success({ message: '编辑Topic成功' });
});
},
};
wrapper.open(xFormModal);
};
export const showLogicalClusterOpModal = (clusterId: number, record?: ILogicalCluster) => {
let clusterModes = [] as IConfigInfo[];
clusterModes = cluster.clusterModes ? cluster.clusterModes : clusterModes;
const xFormModal = {
formMap: [
{
key: 'logicalClusterName',
label: '逻辑集群名称',
rules: [{ required: true, message: '请输入逻辑集群名称' }],
attrs: {
disabled: record ? true : false,
},
},
{
key: 'appId',
label: '所属应用',
rules: [{ required: true, message: '请选择所属应用' }],
type: 'select',
options: app.adminAppData.map(item => {
return {
label: item.name,
value: item.appId,
};
}),
attrs: {
placeholder: '请选择所属应用',
},
},
{
key: 'mode',
label: '集群模式',
type: 'select',
rules: [{ required: true, message: '请选择集群模式' }],
options: clusterModes.map(item => {
return {
label: item.message,
value: item.code,
};
}),
attrs: {
},
},
{
key: 'regionIdList',
label: 'RegionIdList',
type: 'select',
defaultValue: [] as any,
options: admin.brokersRegions.map(item => {
return {
label: item.name,
value: item.id,
};
}),
rules: [{ required: true, message: '请选择BrokerIdList' }],
attrs: {
mode: 'multiple',
placeholder: '请选择BrokerIdList',
},
},
{
key: 'description',
label: '备注',
type: 'text_area',
rules: [{
required: false,
}],
attrs: {
placeholder: '请输入备注',
},
},
],
formData: record,
visible: true,
title: '新增逻辑集群',
onSubmit: (value: INewLogical) => {
const params = {
appId: value.appId,
clusterId,
description: value.description,
id: record ? record.logicalClusterId : '',
mode: value.mode,
name: value.logicalClusterName,
regionIdList: value.regionIdList,
} as INewLogical;
if (record) {
return admin.editLogicalClusters(clusterId, params).then(data => {
notification.success({ message: '编辑逻辑集群成功' });
});
}
return admin.createLogicalClusters(clusterId, params).then(data => {
notification.success({ message: '新建逻辑集群成功' });
});
},
};
wrapper.open(xFormModal);
};
export const showClusterRegionOpModal = (clusterId: number, content: IMetaData, record?: IBrokersRegions) => {
const xFormModal = {
formMap: [
{
key: 'name',
label: 'Region名称',
rules: [{ required: true, message: '请输入Region名称' }],
attrs: { placeholder: '请输入Region名称' },
},
{
key: 'clusterName',
label: '集群名称',
rules: [{ required: true, message: '请输入集群名称' }],
defaultValue: content.clusterName,
attrs: {
disabled: true,
placeholder: '请输入集群名称',
},
},
{
key: 'brokerIdList',
label: 'Broker列表',
defaultValue: record ? record.brokerIdList.join(',') : [] as any,
rules: [{ required: true, message: '请输入BrokerIdList' }],
attrs: {
placeholder: '请输入BrokerIdList',
},
},
{
key: 'status',
label: '状态',
type: 'select',
options: [
{
label: '正常',
value: 0,
},
{
label: '容量已满',
value: 1,
},
],
defaultValue: 0,
rules: [{ required: true, message: '请选择状态' }],
attrs: {
placeholder: '请选择状态',
},
},
{
key: 'description',
label: '备注',
type: 'text_area',
rules: [{
required: false,
}],
attrs: {
placeholder: '请输入备注',
},
},
],
formData: record,
visible: true,
title: `${record ? '编辑' : '新增Region'}`,
onSubmit: (value: INewRegions) => {
value.clusterId = clusterId;
value.brokerIdList = value.brokerIdList && Array.isArray(value.brokerIdList) ?
value.brokerIdList : value.brokerIdList.split(',');
if (record) {
value.id = record.id;
}
delete value.clusterName;
if (record) {
return admin.editRegions(clusterId, value).then(data => {
notification.success({ message: '编辑Region成功' });
});
}
return admin.addNewRegions(clusterId, value).then(data => {
notification.success({ message: '新建Region成功' });
});
},
};
wrapper.open(xFormModal);
};

View File

@@ -0,0 +1,149 @@
import * as React from 'react';
import { admin } from 'store/admin';
import { notification, Modal, Form, Input, Switch, Select, Tooltip } from 'antd';
import { IBrokersMetadata, IBrokersRegions, IExpand } from 'types/base-type';
import { searchProps } from 'constants/table';
import { expandPartition } from 'lib/api';
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 15 },
};
interface IXFormProps {
form: any;
formData?: any;
visible?: boolean;
handleVisible?: any;
clusterId?: number;
}
class CustomForm extends React.Component<IXFormProps> {
public state = {
checked: false,
};
public onSwitchChange(checked: boolean) {
this.setState({ checked });
this.props.form.validateFields((err: any, values: any) => {
checked ? values.brokerIdList = [] : values.regionId = '';
});
}
public handleExpandOk() {
this.props.form.validateFields((err: any, values: any) => {
if (!err) {
this.props.handleVisible(false);
const params = {
topicName: values.topicName,
clusterId: this.props.clusterId,
partitionNum: values.partitionNum,
} as IExpand;
if (values.brokerIdList) {
params.brokerIdList = values.brokerIdList;
} else {
params.regionId = values.regionId;
}
const valueParams = [] as IExpand[];
valueParams.push(params);
expandPartition(valueParams).then(data => {
notification.success({ message: '扩分成功' });
this.props.form.resetFields();
admin.getClusterTopics(this.props.clusterId);
});
}
});
}
public handleExpandCancel() {
this.props.handleVisible(false);
this.props.form.resetFields();
}
public componentDidMount() {
admin.getBrokersMetadata(this.props.clusterId);
admin.getBrokersRegions(this.props.clusterId);
}
public render() {
const { formData = {} as any, visible } = this.props;
const { getFieldDecorator } = this.props.form;
let metadata = [] as IBrokersMetadata[];
metadata = admin.brokersMetadata ? admin.brokersMetadata : metadata;
let regions = [] as IBrokersRegions[];
regions = admin.brokersRegions ? admin.brokersRegions : regions;
return (
<Modal
title="Topic扩分区"
visible={visible}
onOk={() => this.handleExpandOk()}
onCancel={() => this.handleExpandCancel()}
maskClosable={false}
okText="确认"
cancelText="取消"
>
<Form {...layout} name="basic" onSubmit={() => ({})} >
<Form.Item label="Topic名称" >
{getFieldDecorator('topicName', {
initialValue: formData.topicName,
rules: [{ required: true, message: '请输入Topic名称' }],
})(<Input disabled={true} placeholder="请输入Topic名称" />)}
</Form.Item>
<Form.Item label="分区数" >
{getFieldDecorator('partitionNum', {
rules: [{ required: true,
message: '请输入分区数' }],
})(<Input placeholder="请输入分区数" />)}
</Form.Item>
<Form.Item label={this.state.checked ? 'Region类型' : 'Borker类型'} >
<Switch onChange={(checked) => this.onSwitchChange(checked)} />
</Form.Item>
<Form.Item label="brokerIdList" style={{ display: this.state.checked ? 'none' : '' }}>
{getFieldDecorator('brokerIdList', {
initialValue: formData.brokerIdList,
rules: [{ required: !this.state.checked, message: '请输入brokerIdList' }],
})(
<Select
mode="multiple"
{...searchProps}
>
{ metadata.map((v, index) => (
<Select.Option
key={v.brokerId || v.key || index}
value={v.brokerId}
>
{v.host.length > 16 ?
<Tooltip placement="bottomLeft" title={v.host}> {v.host} </Tooltip>
: v.host}
</Select.Option>
))}
</Select>,
)}
</Form.Item>
<Form.Item label="regionId" style={{ display: this.state.checked ? '' : 'none' }} >
{getFieldDecorator('regionId', {
initialValue: formData.regionId,
rules: [{ required: this.state.checked, message: '请选择regionId' }],
})(
<Select {...searchProps}>
{ regions.map((v, index) => (
<Select.Option
key={v.id || v.key || index}
value={v.id}
>
{v.name.length > 16 ?
<Tooltip placement="bottomLeft" title={v.name}> {v.name} </Tooltip>
: v.name}
</Select.Option>
))}
</Select>,
)}
</Form.Item>
</Form>
</Modal>
);
}
}
export const ExpandPartitionFormWrapper = Form.create<IXFormProps>()(CustomForm);

View File

@@ -0,0 +1,5 @@
export * from './user';
export * from './version';
export * from './cluster';
export * from './task';
export * from './migration';

View File

@@ -0,0 +1,186 @@
import * as React from 'react';
import { Table, notification, Button, Modal, Input, Form, Select, message, Tooltip } from 'component/antd';
import { IBrokersMetadata, IRebalance } from 'types/base-type';
import { admin } from 'store/admin';
import { implementRegions, rebalanceStatus } from 'lib/api';
import { searchProps } from 'constants/table';
interface IXFormProps {
form: any;
changeVisible?: (visible: boolean) => any;
visible?: boolean;
clusterId?: number;
clusterName?: string;
}
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 15 },
};
class LeaderRebalanceModal extends React.Component<IXFormProps> {
public brokerId: number;
public host: string;
public metadata = [] as IBrokersMetadata[];
public timer = null as any;
public state = {
imVisible: false,
status: '',
};
public handleRebalanceCancel() {
this.props.changeVisible(false);
this.props.form.resetFields();
this.setState({ imVisible: false });
clearInterval(this.timer);
}
public onMetaChange(value: number) {
this.brokerId = value;
this.metadata.forEach((element: IBrokersMetadata) => {
if (element.brokerId === value) {
this.host = element.host;
}
});
}
public handleSubmit = (e: any) => {
e.preventDefault();
this.props.form.validateFields((err: any, values: any) => {
if (!err) {
let params = {} as IRebalance;
params = {
clusterId: this.props.clusterId,
brokerId: values.brokerId,
dimension: 2,
regionId: 0,
topicName: '',
};
implementRegions(params).then(data => {
message.success('获取成功');
this.getStatus();
this.setState({
imVisible: true,
});
});
}
});
}
// 定时器
public iTimer = () => {
this.timer = setInterval(() => {
this.getStatus();
}, 3000);
}
public getStatus() {
rebalanceStatus(this.props.clusterId).then((data: any) => {
message.success('状态更新成功');
if (data.code === 30) { // code -1 未知 101 成功 30 运行中
setTimeout(this.iTimer, 0);
} else {
this.setState({ status: data.message });
clearInterval(this.timer);
}
});
}
// 组件清除时清除定时器
public componentWillUnmount() {
clearInterval(this.timer);
}
public render() {
const { visible } = this.props;
const reblanceData = [
{
clusterName: this.props.clusterName,
host: this.host,
status: this.state.status,
},
];
const columns = [
{
title: '集群名称',
dataIndex: 'clusterName',
key: 'clusterName',
},
{
title: 'BrokerHost',
dataIndex: 'host',
key: 'host',
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
},
];
this.metadata = admin.brokersMetadata ? admin.brokersMetadata : this.metadata;
const { getFieldDecorator } = this.props.form;
return (
<>
<Modal
visible={visible}
title="Leader Rebalance"
onCancel={() => this.handleRebalanceCancel()}
maskClosable={false}
footer={null}
>
<Form {...layout} name="basic" onSubmit={this.handleSubmit} >
<Form.Item label="集群名称" >
{getFieldDecorator('clusterName', {
initialValue: this.props.clusterName,
rules: [{ required: true, message: '请输入集群名称' }],
})(<Input disabled={true} />)}
</Form.Item>
<Form.Item label="Broker" >
{getFieldDecorator('brokerId', {
rules: [{ required: true, message: '请输入Broker' }],
})(
<Select
onChange={(value: number) => this.onMetaChange(value)}
{...searchProps}
>
{this.metadata.map((v, index) => (
<Select.Option
key={v.brokerId || v.key || index}
value={v.brokerId}
>
{v.host.length > 16 ?
<Tooltip placement="bottomLeft" title={v.host}> {v.host} </Tooltip>
: v.host}
</Select.Option>
))}
</Select>)}
</Form.Item>
<Form.Item label="" >
{getFieldDecorator('submit')(
<Button
htmlType="submit"
type="primary"
className="implement-button"
>
</Button>,
)}
</Form.Item>
{
this.state.imVisible && <Table
rowKey="clusterName"
bordered={true}
dataSource={reblanceData}
columns={columns}
pagination={false}
/>
}
</Form>
</Modal>
</>
);
}
}
export const LeaderRebalanceWrapper = Form.create<IXFormProps>()(LeaderRebalanceModal);

View File

@@ -0,0 +1,303 @@
import { wrapper } from 'store';
import { IReassignTasks, IExecute, IReassign, INewBulidEnums, IEnumsMap } from 'types/base-type';
import { notification } from 'component/antd';
import { expert } from 'store/expert';
import { transMBToB, transBToMB } from 'lib/utils';
import moment = require('moment');
import { admin } from 'store/admin';
import { timeFormat } from 'constants/strategy';
export const startMigrationTask = (item: IReassignTasks, action: string) => {
const params = {
action,
beginTime: +moment(item.beginTime).format('x'),
taskId: item.taskId,
} as IExecute;
expert.getExecuteTask(params).then(data => {
notification.success({ message: '操作成功' });
});
};
export const modifyMigrationTask = (item: IReassignTasks, action: string) => {
const status: number = item.status;
const xFormModal = {
formMap: [
{
key: 'beginTime',
label: '计划开始时间',
type: 'date_picker',
rules: [{
required: status === 0,
message: '请输入计划开始时间',
}],
attrs: {
placeholder: '请输入计划开始时间',
format: timeFormat,
showTime: true,
disabled: status !== 0,
},
},
],
formData: {
beginTime: moment(item.beginTime),
},
visible: true,
title: '操作迁移任务',
onSubmit: (value: IExecute) => {
const params = {
action,
beginTime: +moment(value.beginTime).format('x'),
taskId: item.taskId,
} as IExecute;
expert.getExecuteTask(params).then(data => {
notification.success({ message: '操作成功' });
});
},
};
wrapper.open(xFormModal);
};
export const modifyTransferTask = (item: IReassign, action: string, taskId: number) => {
const status: number = item.status;
const xFormModal = {
formMap: [
{
key: 'throttle',
label: '初始限流',
rules: [{
required: true,
message: '请输入初始限流',
}],
attrs: {
placeholder: '请输入初始限流',
suffix: 'MB/s',
},
},
{
key: 'maxThrottle',
label: '限流上限',
rules: [{
required: true,
message: '请输入限流上限',
}],
attrs: {
placeholder: '请输入限流上限',
suffix: 'MB/s',
},
},
{
key: 'minThrottle',
label: '限流下限',
rules: [{
required: true,
message: '请输入限流下限',
}],
attrs: {
placeholder: '请输入限流下限',
suffix: 'MB/s',
},
},
],
formData: {
throttle: transBToMB(item.realThrottle),
maxThrottle: transBToMB(item.maxThrottle),
minThrottle: transBToMB(item.minThrottle),
},
visible: true,
title: '修改',
onSubmit: (value: IExecute) => {
const params = {
action,
throttle: transMBToB(value.throttle),
maxThrottle: transMBToB(value.maxThrottle),
minThrottle: transMBToB(value.minThrottle),
subTaskId: item.subTaskId,
} as IExecute;
expert.getExecuteSubTask(params, taskId).then(data => {
notification.success({ message: '操作成功' });
});
},
};
wrapper.open(xFormModal);
};
const updateFormModal = () => {
const formMap = wrapper.xFormWrapper.formMap;
formMap[2].options = admin.packageList;
formMap[3].options = admin.serverPropertiesList;
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, wrapper.xFormWrapper.formData);
};
const updateFormExclude = (value: boolean) => {
const formMap = wrapper.xFormWrapper.formMap;
if (value) {
formMap[4].invisible = false;
formMap[5].invisible = false;
formMap[6].invisible = true;
formMap[4].rules = [{
required: true,
}];
formMap[5].rules = [{
required: false,
}];
formMap[6].rules = [{
required: false,
}];
} else {
formMap[4].invisible = true;
formMap[5].invisible = true;
formMap[6].invisible = false;
formMap[4].rules = [{
required: false,
}];
formMap[5].rules = [{
required: false,
}];
formMap[6].rules = [{
required: true,
}];
}
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, wrapper.xFormWrapper.formData);
};
export const addMigrationTask = () => {
const taskStatus = admin.configsTaskStatus ? admin.configsTaskStatus : [] as IEnumsMap[];
const xFormModal = {
formMap: [
{
key: 'clusterId',
label: '集群',
type: 'select',
options: admin.metaList.map(item => {
return {
label: item.clusterName,
value: item.clusterId,
};
}),
rules: [{
required: true,
}],
attrs: {
placeholder: '请选择集群',
onChange: (value: number) => {
admin.getTasksKafkaFiles(value).then(() => {
updateFormModal();
});
},
},
},
{
key: 'taskType',
label: '任务类型',
type: 'select',
options: admin.tasksEnums,
rules: [{
required: true,
message: '请选择集群任务',
}],
attrs: {
placeholder: '请选择集群任务',
onChange: (value: string) => {
value === 'role_upgrade' ? updateFormExclude(true) : updateFormExclude(false);
},
},
},
{
key: 'kafkafileNameMd5',
label: '包版本',
type: 'select',
options: admin.packageList,
rules: [{
required: true,
message: '请选择包版本',
}],
attrs: {
placeholder: '请选择包版本',
},
},
{
key: 'serverfileNameMd5',
label: 'server配置',
type: 'select',
options: admin.serverPropertiesList,
rules: [{
required: true,
message: '请选择server配置',
}],
attrs: {
placeholder: '请选择server配置',
},
},
{
key: 'upgradeSequenceList',
label: '升级顺序',
type: 'select',
options: admin.kafkaRoles.map(item => {
return {
label: item.role,
value: item.role,
};
}),
rules: [{
required: true,
message: '请输入升级顺序',
}],
defaultValue: [] as any,
attrs: {
mode: 'multiple',
placeholder: '请选择升级顺序',
},
},
{
key: 'ignoreList',
label: '排除主机列表',
type: 'select',
invisible: true,
rules: [{
required: false,
message: '请输入排除主机列表',
}],
defaultValue: [] as any,
attrs: {
placeholder: '请输入排除主机列表',
mode: 'tags',
tokenSeparators: [','],
},
},
{
key: 'hostList',
label: '主机列表',
type: 'select',
rules: [{
required: true,
message: '请输入主机列表',
}],
defaultValue: [] as any,
attrs: {
placeholder: '请输入主机列表',
mode: 'tags',
tokenSeparators: [' '],
},
},
],
formData: {},
visible: true,
title: '新建集群任务',
onSubmit: (value: INewBulidEnums) => {
value.kafkaPackageName = value.kafkafileNameMd5.split(',')[0];
value.kafkaPackageMd5 = value.kafkafileNameMd5.split(',')[1];
value.serverPropertiesName = value.serverfileNameMd5.split(',')[0];
value.serverPropertiesMd5 = value.serverfileNameMd5.split(',')[1];
delete value.kafkafileNameMd5;
delete value.serverfileNameMd5;
admin.addMigrationTask(value).then(data => {
notification.success({ message: '新建集群任务成功' });
});
},
};
wrapper.open(xFormModal);
};

View File

@@ -0,0 +1,277 @@
import { wrapper } from 'store';
import { notification } from 'component/antd';
import { expert } from 'store/expert';
import { admin } from 'store/admin';
import { IMigration, IReassignTasks, IExecute } from 'types/base-type';
import { createMigrationTask } from 'lib/api';
import { transMBToB, transHourToMSecond, transMSecondToHour } from 'lib/utils';
import moment = require('moment');
import { timeFormat } from 'constants/strategy';
const updateFormModal = (topicName?: string) => {
const formMap = wrapper.xFormWrapper.formMap;
const formData = wrapper.xFormWrapper.formData;
if (topicName) {
formMap[5].options = expert.partitionIdMap[topicName]; // 3
formData.originalRetentionTime = transMSecondToHour(admin.topicsBasic.retentionTime);
} else {
formMap[1].options = expert.taskTopicMetadata;
formMap[3].options = admin.brokersMetadata; // 2
formMap[4].options = admin.brokersRegions;
}
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, wrapper.xFormWrapper.formData, !!topicName, ['partitionIdList']);
};
const updateInputModal = (status?: string) => {
const formMap = wrapper.xFormWrapper.formMap;
formMap[3].invisible = status === 'region';
formMap[4].invisible = status !== 'region';
formMap[3].rules = [{required: status !== 'region'}];
formMap[4].rules = [{required: status === 'region'}];
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, wrapper.xFormWrapper.formData);
};
let clusterId = 0 as number;
export const createMigrationTasks = () => {
const xFormModal = {
type: 'drawer',
width: 700,
formMap: [
{
key: 'clusterId',
label: '集群名称',
type: 'select',
options: expert.metaData ? expert.metaData.slice(1).map(item => {
return {
label: item.clusterName,
value: item.clusterId,
};
}) : [],
rules: [{
required: true,
}],
attrs: {
async onChange(value: number) {
clusterId = value;
await admin.getBrokersMetadata(value);
await admin.getBrokersRegions(value);
await expert.getTaskTopicMetadata(value);
updateFormModal();
},
},
},
{
key: 'topicName',
label: 'Topic名称',
type: 'select',
options: (expert.taskTopicMetadata),
rules: [{
required: true,
}],
attrs: {
showSearch: true,
optionFilterProp: 'children',
async onChange(value: string) {
await admin.getTopicsBasicInfo(clusterId, value);
updateFormModal(value);
},
},
},
{
key: 'species',
label: '类型',
type: 'radio_group',
defaultValue: 'broker',
options: [{
label: 'Region',
value: 'region',
}, {
label: 'Borker',
value: 'broker',
}],
rules: [{
required: false,
message: '请选择类型',
}],
attrs: {
onChange(item: any) {
updateInputModal(item.target.value);
},
},
},
{
key: 'brokerIdList',
label: 'Broker',
type: 'select',
defaultValue: [] as any,
invisible: false,
options: admin.brokersMetadata,
rules: [{ required: true, message: '请选择Broker' }],
attrs: {
mode: 'multiple',
placeholder: '请选择Broker',
},
},
{
key: 'regionId',
label: 'Region',
type: 'select',
defaultValue: [] as any,
invisible: true,
options: admin.brokersRegions,
rules: [{ required: false, message: '请选择Region' }],
attrs: {
placeholder: '请选择Region',
},
},
{
key: 'partitionIdList',
label: '分区ID',
type: 'select',
defaultValue: [] as any,
rules: [{
required: false,
}],
attrs: {
mode: 'tags',
placeholder: '请选择PartitionIdList',
},
},
{
key: 'beginTime',
label: '计划开始时间',
type: 'date_picker',
rules: [{
required: true,
message: '请输入计划开始时间',
}],
attrs: {
placeholder: '请输入计划开始时间',
format: timeFormat,
showTime: true,
},
},
{
key: 'originalRetentionTime',
label: '原本保存时间',
rules: [{
required: true,
message: '请输入原本保存时间',
}],
attrs: {
disabled: true,
placeholder: '请输入原本保存时间',
suffix: '小时',
},
},
{
key: 'reassignRetentionTime',
label: '迁移保存时间',
rules: [{
required: true,
message: '请输入迁移保存时间',
}],
attrs: {
placeholder: '请输入迁移保存时间',
suffix: '小时',
},
},
{
key: 'throttle',
label: '初始限流',
rules: [{
required: true,
message: '请输入初始限流',
}],
attrs: {
placeholder: '请输入初始限流',
suffix: 'MB/s',
},
},
{
key: 'maxThrottle',
label: '限流上限',
rules: [{
required: true,
message: '请输入限流上限',
}],
attrs: {
placeholder: '请输入限流上限',
suffix: 'MB/s',
},
},
{
key: 'minThrottle',
label: '限流下限',
rules: [{
required: true,
message: '请输入限流下限',
}],
attrs: {
placeholder: '请输入限流下限',
suffix: 'MB/s',
},
},
{
key: 'description',
label: '备注',
type: 'text_area',
rules: [{
required: false,
message: '请输入至少5个字符',
pattern: /^.{5,}.$/,
}],
attrs: {
placeholder: '请输入备注',
},
},
],
formData: {},
visible: true,
title: '新建迁移任务',
onSubmit: (value: any) => {
const params = {
clusterId: value.clusterId,
beginTime: +moment(value.beginTime).format('x'),
originalRetentionTime: transHourToMSecond(value.originalRetentionTime),
reassignRetentionTime: transHourToMSecond(value.reassignRetentionTime),
throttle: transMBToB(value.throttle),
maxThrottle: transMBToB(value.maxThrottle),
minThrottle: transMBToB(value.minThrottle),
description: value.description,
brokerIdList: value.brokerIdList,
regionId: value.regionId,
partitionIdList: value.partitionIdList,
topicName: value.topicName,
} as IMigration;
if (value.regionId) {
delete params.brokerIdList;
} else {
delete params.regionId;
}
createMigrationTask([params]).then(data => {
notification.success({ message: '新建迁移任务成功' });
expert.getReassignTasks();
});
},
};
wrapper.open(xFormModal);
};
export const cancelMigrationTask = (item: IReassignTasks, action: string) => {
const params = {
action,
taskId: item.taskId,
beginTime: +moment(item.beginTime).format('x'),
throttle: Number(item.throttle),
maxThrottle: Number(item.maxThrottle),
minThrottle: Number(item.minThrottle),
} as IExecute;
expert.getExecuteTask(params).then(data => {
notification.success({ message: '操作成功' });
});
};

View File

@@ -0,0 +1,46 @@
import { IUser } from 'types/base-type';
import { users } from 'store/users';
import { wrapper } from 'store';
import { roleMap } from 'constants/status-map';
import { message } from 'component/antd';
import { FormItemType } from 'component/x-form';
export const showApplyModal = (record?: IUser) => {
const xFormModal = {
formMap: [
{
key: 'username',
label: '用户名',
rules: [{ required: true, message: '请输入用户名' }],
}, {
key: 'role',
label: '角色',
type: 'select',
options: Object.keys(roleMap).map((item) => ({
label: roleMap[+item],
value: +item,
})),
rules: [{ required: true, message: '请选择角色' }],
}, {
key: 'password',
label: '密码',
type: FormItemType.inputPassword,
rules: [{ required: !record, message: '请输入密码' }],
},
],
formData: record || {},
visible: true,
title: record ? '修改用户信息' : '新增用户',
onSubmit: (value: IUser) => {
if (record) {
return users.modfiyUser(value).then(() => {
message.success('操作成功');
});
}
return users.addUser(value).then(() => {
message.success('操作成功');
});
},
};
wrapper.open(xFormModal);
};

View File

@@ -0,0 +1,191 @@
import { notification } from 'component/antd';
import { IUploadFile, IConfigure } from 'types/base-type';
import { version } from 'store/version';
import { admin } from 'store/admin';
import { wrapper } from 'store';
import { computeChecksumMd5 } from 'lib/utils';
const handleSelectChange = (e: number) => {
version.setAcceptFileType(e);
updateFormModal(e);
};
export const showUploadModal = () => {
const xFormModal = {
formMap: [
{
key: 'fileType',
label: '文件类型',
type: 'select',
options: version.fileTypeList,
attrs: {
onChange: (e: number) => handleSelectChange(e),
},
rules: [{ required: true, message: '请选择文件类型' }],
}, {
key: 'clusterId',
label: '集群',
type: 'select',
invisible: true,
options: admin.metaList.map(item => ({
...item,
label: item.clusterName,
value: item.clusterId,
})),
rules: [{ required: false, message: '请选择集群' }],
}, {
key: 'uploadFile',
label: '上传文件',
type: 'upload',
attrs: {
accept: version.fileSuffix,
},
rules: [{
required: true,
validator: (rule: any, value: any, callback: any) => {
if (value.length) {
if (value.length > 1) {
callback('一次仅支持上传一份文件!');
return false;
}
return true;
} else {
callback(`请上传文件`);
return false;
}
},
}],
}, {
key: 'description',
label: '备注',
type: 'text_area',
rules: [{ required: false, message: '请输入备注' }],
},
],
formData: {},
visible: true,
title: '上传',
onText: '保存',
isWaitting: true,
onSubmit: (value: IUploadFile) => {
value.file = value.uploadFile[0].originFileObj;
return computeChecksumMd5(value.file).then(md5 => {
const params = {
fileName: value.file.name,
fileMd5: md5,
clusterId: value.clusterId || -1,
...value,
};
return version.addFile(params);
});
},
};
wrapper.open(xFormModal);
};
const updateFormModal = (type: number) => {
const formMap = wrapper.xFormWrapper.formMap;
if (formMap && formMap.length > 2) {
formMap[1].invisible = !version.currentFileType;
formMap[1].rules = [{ required: version.currentFileType, message: '请上传文件' }];
formMap[2].attrs = {
accept: version.fileSuffix,
},
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, wrapper.xFormWrapper.formData, true);
}
};
export const showModifyModal = (record: IUploadFile) => {
version.setAcceptFileType(record.fileType);
const xFormModal = {
formMap: [
{
key: 'uploadFile',
label: '上传文件',
type: 'upload',
attrs: {
accept: version.fileSuffix,
},
rules: [{
required: true,
validator: (rule: any, value: any, callback: any) => {
if (value.length) {
if (value.length > 1) {
callback('一次仅支持上传一份文件!');
return false;
}
return true;
} else {
callback(`请上传文件`);
return false;
}
},
}],
}, {
key: 'description',
label: '备注',
type: 'text_area',
rules: [{ required: false, message: '请输入备注' }],
},
],
formData: record || {},
visible: true,
isWaitting: true,
title: '修改',
onSubmit: async (value: IUploadFile) => {
value.file = value.uploadFile[0].originFileObj;
const md5 = await computeChecksumMd5(value.file);
const params = {
fileName: value.file.name,
fileMd5: md5 as string,
description: value.description,
file: value.file,
id: record.id,
};
return version.modfiyFile(params);
},
};
wrapper.open(xFormModal);
};
export const showConfigureModal = (record?: IConfigure) => {
const xFormModal = {
formMap: [
{
key: 'configKey',
label: '配置键',
rules: [{ required: true, message: '请输入配置键' }],
attrs: {
disabled: record ? true : false,
},
}, {
key: 'configValue',
label: '配置值',
type: 'text_area',
rules: [{ required: true, message: '请输入配置值' }],
}, {
key: 'configDescription',
label: '备注',
type: 'text_area',
rules: [{ required: true, message: '请输入备注' }],
},
],
formData: record || {},
visible: true,
isWaitting: true,
title: `${record ? '修改配置' : '新建配置'}`,
onSubmit: async (value: IConfigure) => {
if (record) {
return admin.editConfigure(value).then(data => {
notification.success({ message: '修改配置成功' });
});
}
return admin.addNewConfigure(value).then(data => {
notification.success({ message: '新建配置成功' });
});
},
};
wrapper.open(xFormModal);
};

View File

@@ -0,0 +1,80 @@
import { notification } from 'component/antd';
import { wrapper, region } from 'store';
import { IMonitorSilences } from 'types/base-type';
import { alarm } from 'store/alarm';
import { urlPrefix } from 'constants/left-menu';
import moment from 'moment';
import { timeFormat } from 'constants/strategy';
export const createMonitorSilences = (monitorId: number, monitorName: string) => {
const xFormWrapper = {
formMap: [
{
key: 'monitorName',
label: '告警名称',
rules: [{
required: true,
message: '请输入告警名称',
}],
attrs: {
disabled: true,
},
},
{
key: 'beginEndTime',
label: '开始~结束时间',
type: 'range_picker',
rules: [{
required: true,
message: '请输入开始~结束时间',
}],
attrs: {
placeholder: ['开始时间', '结束时间'],
format: timeFormat,
showTime: true,
ranges: {
'1小时': [moment(), moment().add(1, 'hour')],
'2小时': [moment(), moment().add(2, 'hour')],
'6小时': [moment(), moment().add(6, 'hour')],
'12小时': [moment(), moment().add(12, 'hour')],
'1天': [moment(), moment().add(1, 'day')],
'2天': [moment(), moment().add(7, 'day')],
'7天': [moment(), moment().add(7, 'day')],
},
},
},
{
key: 'description',
label: '说明',
type: 'text_area',
rules: [{
required: true,
message: '请输入说明',
}],
attrs: {
placeholder: '请输入备注',
},
},
],
formData: {
monitorName,
},
okText: '确认',
visible: true,
width: 600,
title: '屏蔽',
onSubmit: (value: any) => {
const params = {
description: value.description,
startTime: +moment(value.beginEndTime[0]).format('x'),
endTime: +moment(value.beginEndTime[1]).format('x'),
monitorId,
} as IMonitorSilences;
alarm.createSilences(params, monitorId).then(data => {
notification.success({ message: '屏蔽成功' });
window.location.href = `${urlPrefix}/alarm/alarm-detail?id=${monitorId}&region=${region.currentRegion}#3`;
});
},
};
wrapper.open(xFormWrapper);
};

View File

@@ -0,0 +1,127 @@
import { IAppItem, IOrderParams } from 'types/base-type';
import * as React from 'react';
import { wrapper, region } from 'store';
import { users } from 'store/users';
import { app } from 'store/app';
import { StaffSelect } from 'container/staff-select';
import { message, Icon } from 'component/antd';
import { urlPrefix } from 'constants/left-menu';
import { copyString } from 'lib/utils';
export const showEditModal = (record?: IAppItem, from?: string, isDisabled?: boolean) => {
const xFormModal = {
formMap: [
{
key: 'appId',
label: '应用ID',
invisible: !record,
defaultValue: record && record.appId || '',
rules: [{
required: isDisabled ? false : record && record.appId,
message: '请输入不得超过64个字符',
pattern: /^.{1,64}$/,
}],
attrs: { disabled: true },
}, {
key: 'name',
label: '应用名称',
defaultValue: record && record.name || '',
rules: [{
required: isDisabled ? false : true,
message: '请输入不得超过64个字符',
pattern: /^.{1,64}$/,
}],
attrs: { disabled: isDisabled },
}, {
key: 'password',
label: '密码',
invisible: !record,
defaultValue: record && record.password || '',
rules: [{
required: isDisabled ? false : record && record.password,
message: '请输入不得超过64个字符',
pattern: /^.{1,64}$/,
}],
attrs: {
disabled: true,
suffix: (
<Icon
onClick={() => copyString(record.password)}
type="copy"
className="icon-color"
/>),
},
}, {
key: 'idc',
label: '数据中心',
defaultValue: region.regionName,
rules: [{ required: isDisabled ? false : true, message: '请输入' }],
attrs: {
placeholder: '请输入',
disabled: true,
},
}, {
key: 'principalList',
label: '负责人',
type: 'custom',
customFormItem: <StaffSelect isDisabled={isDisabled}/>,
rules: [{
required: isDisabled ? false : true,
message: '请选择负责人(至少两人)',
validator: (rule: any, value: []) => {
if (value.length < 2) {
return false;
}
return true;
},
}],
}, {
key: 'description',
label: '应用描述',
type: 'text_area',
rules: [{ required: isDisabled ? false : true, message: '请输入描述'}],
attrs: { disabled: isDisabled },
},
],
formData: record,
visible: true,
title: `${isDisabled ? '详情' : record ? '修改' : '应用申请'}`,
onSubmit: (value: IAppItem) => {
if (isDisabled) {
return;
}
value.idc = region.currentRegion;
return operateApp(!!record, value, record, from).then((data: any) => {
message.success('操作成功');
if (!record) {
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
}
});
},
};
wrapper.open(xFormModal);
};
const operateApp = (isEdit: boolean, value: IAppItem, record?: IAppItem, from?: string) => {
const params: IOrderParams = {
description: value.description,
type: 1,
applicant: users.currentUser.username,
};
let principals = '';
if (value.principalList && value.principalList.length) {
principals = value.principalList.join(',');
}
params.extensions = JSON.stringify({ principals, idc: value.idc, name: value.name });
let modifyParams = {};
if (isEdit) {
modifyParams = {
appId: record.appId,
description: value.description,
name: value.name,
principals,
};
}
return isEdit ? app.modfiyApplication(modifyParams, from) : app.applyApplication(params);
};

View File

@@ -0,0 +1,181 @@
import * as React from 'react';
import { Table, Modal, Tooltip, Icon, message, notification } from 'component/antd';
import { app } from 'store/app';
import { observer } from 'mobx-react';
import { modal } from 'store/modal';
import { users } from 'store/users';
import { urlPrefix } from 'constants/left-menu';
import { region } from 'store';
import { topic, IConnectionInfo } from 'store/topic';
import urlQuery from 'store/url-query';
import { cellStyle } from 'constants/table';
import { XFormComponent } from 'component/x-form';
import { ILimitsItem } from 'types/base-type';
const formLayout = {
labelCol: { span: 2 },
wrapperCol: { span: 16 },
};
@observer
export class CancelTopicPermission extends React.Component {
private $formRef: any;
public componentDidMount() {
topic.getConnectionInfo(modal.params.clusterId, modal.params.topicName, urlQuery.appId);
}
public handleCancel = () => {
topic.setConnectionInfo([]);
modal.close();
}
public handleSubmit = () => {
this.$formRef.validateFields((error: Error, value: ILimitsItem) => {
if (error) {
return;
}
if (topic.connectionInfo && topic.connectionInfo.length) {
return message.warning('存在连接信息,无法取消权限!');
}
const appId = urlQuery.appId;
if (value.access.length) {
value.access = value.access.length === 2 ? '3' : value.access[0];
}
const extensions = {
appId,
clusterId: modal.params.clusterId,
topicName: modal.params.topicName,
access: Number(value.access),
};
app.cancelProdPermission({
type: 13,
description: '',
applicant: users.currentUser.username,
extensions: JSON.stringify(extensions),
}).then((data: any) => {
notification.success({ message: '取消权限操作成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
modal.close();
});
}
public getColumns = () => {
const onlineColumns = [
{
title: 'AppID',
dataIndex: 'appId',
key: 'appId',
width: '20%',
sorter: (a: IConnectionInfo, b: IConnectionInfo) => a.appId.charCodeAt(0) - b.appId.charCodeAt(0),
},
{
title: '主机名',
dataIndex: 'hostname',
key: 'hostname',
width: '40%',
onCell: () => ({
style: {
maxWidth: 250,
...cellStyle,
},
}),
render: (t: string) => {
return (
<Tooltip placement="bottomLeft" title={t} >{t}</Tooltip>
);
},
},
{
title: '客户端版本',
dataIndex: 'clientVersion',
key: 'clientVersion',
width: '20%',
},
{
title: '客户端类型',
dataIndex: 'clientType',
key: 'clientType',
width: '20%',
render: (t: string) => <span>{t === 'consumer' ? '消费' : '生产'}</span>,
},
] as any;
return onlineColumns;
}
public render() {
const access: number = modal.params.access;
const consume = access === 0 || access === 1;
const send = access === 0 || access === 2;
const accessStatus = access === 0 ? [] : (access === 1 || access === 2) ? [access + ''] : ['1', '2'];
const formMap = [
{
key: 'access',
label: '权限',
type: 'check_box',
defaultValue: accessStatus,
options: [{
label: '消费权限',
value: '1',
disabled: send,
}, {
label: '发送权限',
value: '2',
disabled: consume,
}],
rules: [{ required: access !== 0, message: '请选择' }],
},
] as any;
return (
<>
<Modal
visible={true}
className="stream-debug-modal"
title={
<span>
{topic.connectionInfo.length ?
<Tooltip placement="right" title={'如若有连接信息,则表示资源正处于使用中,禁止下线操作。如需下线烦请关闭连接信息中的Kafka发送/消费客户端后再进行下线。'} >
<Icon className="question-icon" type="question-circle" />
</Tooltip> : null}
</span>
}
maskClosable={false}
onCancel={this.handleCancel}
onOk={this.handleSubmit}
okText="确定"
cancelText="取消"
okButtonProps={{ disabled: topic.loading || !!topic.connectionInfo.length }}
width={700}
>
<>
<Table
title={() => '连接信息'}
loading={topic.loading}
className="custom-content"
rowKey="key"
scroll={{ x: 450, y: 260 }}
dataSource={topic.connectionInfo}
columns={this.getColumns()}
pagination={false}
bordered={true}
/>
<div className="topic-x-form-box">
<XFormComponent
ref={form => this.$formRef = form}
formData={{}}
formMap={formMap}
formLayout={formLayout}
/>
<div>
</div>
</div>
</>
</Modal>
</>
);
}
}

View File

@@ -0,0 +1,58 @@
import { IClusterData } from 'types/base-type';
import { users } from 'store/users';
import { wrapper } from 'store';
import { cluster } from 'store/cluster';
import { notification } from 'component/antd';
import { urlPrefix } from 'constants/left-menu';
import { region } from 'store';
export const showCpacityModal = (item: IClusterData) => {
const xFormModal = {
formMap: [
{
key: 'type',
label: '扩缩容:',
type: 'select',
options: [{
label: '扩容',
value: 5,
}, {
label: '缩容',
value: 15,
}],
rules: [{ required: true, message: '请选择' }],
attrs: {
placeholder: '请选择',
},
},
{
key: 'description',
label: '申请原因',
type: 'text_area',
rules: [{ required: true, pattern: /^.{5,}.$/, message: '请输入至少5个字符' }],
attrs: {
placeholder: '请输入至少5个字符',
},
},
],
formData: {},
visible: true,
title: '申请扩缩容',
okText: '确认',
onSubmit: (value: any) => {
const cpacityParams = {
type: value.type,
applicant: users.currentUser.username,
description: value.description,
extensions: JSON.stringify({clusterId: item.clusterId}),
};
cluster.applyCpacity(cpacityParams).then(data => {
notification.success({
message: `申请${value.type === 5 ? '扩容' : '缩容'}成功`,
});
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
},
};
wrapper.open(xFormModal);
};

View File

@@ -0,0 +1,81 @@
import * as React from 'react';
import { Table, Modal, Tooltip, Icon, message, notification } from 'component/antd';
import { getApplyOnlineColumns } from 'container/topic/config';
import { observer } from 'mobx-react';
import { modal } from 'store/modal';
import { users } from 'store/users';
import { urlPrefix } from 'constants/left-menu';
import { region } from 'store';
import { topic } from 'store/topic';
@observer
export class ConnectTopicList extends React.Component {
public componentDidMount() {
topic.getConnectionInfo(modal.params.clusterId, modal.params.topicName, modal.params.appId);
}
public handleCancel = () => {
topic.setConnectionInfo([]);
modal.close();
}
public handleSubmit = () => {
const connectionList = topic.connectionInfo;
if (connectionList && connectionList.length) {
return message.warning('存在连接信息,无法申请下线!');
}
const online = {
clusterId: modal.params.clusterId,
topicName: modal.params.topicName,
};
const offlineParams = {
type: 10,
applicant: users.currentUser.username,
description: '',
extensions: JSON.stringify(online),
};
topic.applyTopicOnline(offlineParams).then((data: any) => {
notification.success({ message: '申请下线成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
modal.close();
}
public render() {
return (
<>
<Modal
visible={true}
className="stream-debug-modal"
title={
<span>
线
<Tooltip placement="right" title={' 如若有连接信息,则表示资源正处于使用中,禁止下线操作。如需下线烦请关闭连接信息中的Kafka发送/消费客户端后再进行下线。 '} >
<Icon className="question-icon" type="question-circle" />
</Tooltip>
</span>
}
maskClosable={false}
onCancel={this.handleCancel}
onOk={this.handleSubmit}
okText="下线"
cancelText="取消"
okButtonProps={{ disabled: topic.connectLoading || !!topic.connectionInfo.length }}
width={700}
>
<Table
rowKey="key"
title={() => '连接信息'}
loading={topic.connectLoading}
scroll={{ x: 450, y: 260 }}
dataSource={topic.connectionInfo}
columns={getApplyOnlineColumns()}
pagination={false}
bordered={true}
/>
</Modal>
</>
);
}
}

View File

@@ -0,0 +1,33 @@
import { wrapper } from 'store';
import { IXFormWrapper } from 'types/base-type';
import * as React from 'react';
import { WrappedDataMigrationFormTable } from 'container/drawer/data-migration';
export interface IRenderData {
brokerIdList: number[];
partitionIdList: number[];
topicName: string;
clusterId?: number;
clusterName?: string;
throttle: number;
maxThrottle: number;
minThrottle: number;
originalRetentionTime: number;
reassignRetentionTime: number;
retentionTime: number;
key?: string | number;
}
export const migrationModal = (renderData: IRenderData[]) => {
const xFormWrapper = {
type: 'drawer',
visible: true,
width: 1000,
title: '新建迁移任务',
customRenderElement: <WrappedDataMigrationFormTable data={renderData}/>,
nofooter: true,
noform: true,
};
wrapper.open(xFormWrapper as IXFormWrapper);
};

View File

@@ -0,0 +1,5 @@
export * from './admin';
export * from './app';
export * from './alarm';
export * from './topic';
export * from './cluster';

View File

@@ -0,0 +1,77 @@
import * as React from 'react';
import { Table, Modal, Tooltip, Icon, message, notification } from 'component/antd';
import { app } from 'store/app';
import { getApplyOnlineColumns } from 'container/topic/config';
import { observer } from 'mobx-react';
import { modal } from 'store/modal';
import { users } from 'store/users';
import { urlPrefix } from 'constants/left-menu';
import { region } from 'store';
@observer
export class ConnectAppList extends React.Component {
public componentDidMount() {
app.getAppsConnections(modal.params);
}
public handleCancel = () => {
app.setAppsConnections([]);
modal.close();
}
public handleSubmit = () => {
const connectionList = app.appsConnections;
if (connectionList && connectionList.length) {
return message.warning('存在连接信息,无法申请下线!');
}
const offlineParams = {
type: 11,
applicant: users.currentUser.username,
description: '',
extensions: JSON.stringify({ appId: modal.params }),
};
app.applyAppOffline(offlineParams).then((data: any) => {
notification.success({ message: '申请下线成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
modal.close();
}
public render() {
return (
<>
<Modal
visible={true}
className="stream-debug-modal"
title={
<span>
线
<Tooltip placement="right" title={'如若有连接信息,则表示资源正处于使用中,禁止下线操作。如需下线烦请关闭连接信息中的Kafka发送/消费客户端后再进行下线。'} >
<Icon className="question-icon" type="question-circle" />
</Tooltip>
</span>
}
maskClosable={false}
onCancel={this.handleCancel}
onOk={this.handleSubmit}
okText="下线"
cancelText="取消"
okButtonProps={{ disabled: app.connectLoading || !!app.appsConnections.length }}
width={700}
>
<Table
rowKey="key"
title={() => '连接信息'}
loading={app.connectLoading}
scroll={{ x: 450, y: 260 }}
dataSource={app.appsConnections}
columns={getApplyOnlineColumns()}
pagination={false}
bordered={true}
/>
</Modal>
</>
);
}
}

View File

@@ -0,0 +1,95 @@
import * as React from 'react';
import { Table, Modal, Tooltip, Icon, message, notification } from 'component/antd';
import { app } from 'store/app';
import { observer } from 'mobx-react';
import { modal } from 'store/modal';
import { users } from 'store/users';
import { cellStyle } from 'constants/table';
import { cluster } from 'store/cluster';
@observer
export class OfflineClusterModal extends React.Component {
public componentDidMount() {
cluster.getClusterMetaTopics(modal.params);
}
public handleCancel = () => {
modal.close();
cluster.setClusterTopicsMeta([]);
}
public handleSubmit = () => {
if (cluster.clusterMetaTopics.length) {
return message.warning('存在Topic信息无法申请下线');
}
const offlineParams = {
type: 14,
applicant: users.currentUser.username,
description: '',
extensions: JSON.stringify({clusterId: modal.params}),
};
cluster.applyClusterOffline(offlineParams).then(data => {
notification.success({ message: '申请下线成功' });
});
modal.close();
}
public getColumns = () => {
const offlineColumns = [
{
title: 'Topic列表',
dataIndex: 'topicName',
key: 'topicName',
onCell: () => ({
style: {
maxWidth: 250,
...cellStyle,
},
}),
render: (t: string) => {
return (
<Tooltip placement="bottomLeft" title={t} >{t}</Tooltip>
);
},
},
];
return offlineColumns;
}
public render() {
return (
<>
<Modal
visible={true}
className="stream-debug-modal"
title={
<span>
线
<Tooltip placement="right" title={'如若有topic列表则表示资源正处于使用中禁止下线操作。如需下线烦请下线topic列表所有topic。'} >
<Icon className="question-icon" type="question-circle" />
</Tooltip>
</span>
}
maskClosable={false}
onCancel={this.handleCancel}
onOk={this.handleSubmit}
okText="下线"
cancelText="取消"
okButtonProps={{ disabled: cluster.filterLoading || !!cluster.clusterMetaTopics.length }}
width={700}
>
<Table
rowKey="key"
loading={cluster.filterLoading}
dataSource={cluster.clusterMetaTopics}
columns={this.getColumns()}
scroll={{ x: 300, y: 320 }}
pagination={false}
bordered={true}
/>
</Modal>
</>
);
}
}

View File

@@ -0,0 +1,353 @@
import { wrapper } from 'store';
import { order } from 'store/order';
import { message, Icon, notification, Modal, Table, Tooltip } from 'component/antd';
import { IApprovalOrder, IBaseOrder, IOrderInfo } from 'types/base-type';
import { admin } from 'store/admin';
import { modal } from 'store/modal';
import { cellStyle } from 'constants/table';
import * as React from 'react';
const updateInputModal = (status: string, type: number) => {
const formMap = wrapper.xFormWrapper.formMap;
const region = type === 0 ? 5 : 3;
const broker = type === 0 ? 6 : 4;
formMap[region].invisible = status === 'region';
formMap[broker].invisible = status !== 'region';
formMap[region].rules = type === 0 ? [{ required: status !== 'region' }] : [{ required: false }];
formMap[broker].rules = type === 0 ? [{ required: status === 'region' }] : [{ required: false }];
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, wrapper.xFormWrapper.formData);
};
const renderModalTilte = (type: number, status: number) => {
return (
<>
<span key={'title-1'}></span>
{
type === 3 && status === 1 ? <span key={'subtitle-1'}>
<a target="_blank" href="https://github.com/didi/kafka-manager">
<span className="safe-tip" key={1}></span>&nbsp;
<Icon key={2} type="question-circle" />
</a>
</span> : null}
</>);
};
export const showApprovalModal = (info: IOrderInfo, status: number, from?: string) => {
const { id, type } = info;
const formMap = [{
key: 'partitionNum',
label: '分区数',
type: 'input_number',
rules: [{
required: true,
message: '请输入分区数(正数)',
pattern: /^[1-9]\d*$/,
}],
}, {
key: 'replicaNum',
label: '副本数',
type: 'input_number',
defaultValue: 3,
rules: [{
required: true,
message: '请输入副本数(正数)',
pattern: /^[1-9]\d*$/,
}],
}, {
key: 'retentionTime',
label: '保存时间',
defaultValue: '48',
type: 'select',
options: [{
label: '12小时',
value: '12',
}, {
label: '24小时',
value: '24',
}, {
label: '48小时',
value: '48',
}, {
label: '72小时',
value: '72',
}],
rules: [{
required: true,
message: '请选择',
}],
}, {
key: 'species',
label: '类型',
type: 'radio_group',
defaultValue: 'region',
options: [{
label: 'Region',
value: 'region',
}, {
label: 'Borker',
value: 'broker',
}],
rules: [{ required: false, message: '请选择类型' }],
attrs: {
onChange(item: any) {
updateInputModal(item.target.value, type);
},
},
}, {
key: 'brokerIdList',
label: 'Broker',
invisible: true,
rules: [{ required: false, message: '请输入Broker' }],
attrs: {
placeholder: '请输入Broker',
},
}, {
key: 'regionId',
label: 'Region',
type: 'select',
invisible: false,
options: admin.brokersRegions,
rules: [{ required: true, message: '请选择Region' }],
attrs: {
placeholder: '请选择Region',
},
}] as any;
const quotaFormMap = [{
key: 'nowPartitionNum',
label: '现有分区数',
type: 'input_number',
defaultValue: info.detail.partitionNum || info.detail.presentPartitionNum,
rules: [{ required: false }],
attrs: { disabled: true },
}, {
key: 'regionName',
label: '所属Region',
defaultValue: info.detail.regionNameList,
rules: [{ required: false }],
attrs: { disabled: true },
}, {
key: 'regionBrokerIdList',
label: 'RegionBroker',
defaultValue: info.detail.regionBrokerIdList,
rules: [{ required: false }],
attrs: { disabled: true },
}, {
key: 'topicBrokerIdList',
label: 'TopicBroker',
defaultValue: info.detail.topicBrokerIdList,
rules: [{ required: false }],
attrs: { disabled: true },
}, {
key: 'partitionNum',
label: '新增分区数',
type: 'input_number',
rules: [{
required: type !== 2,
message: '请输入分区数(正数)',
pattern: /^[1-9]\d*$/,
}],
}, {
key: 'brokerIdList',
label: '扩至Broker',
defaultValue: info.detail.regionBrokerIdList,
rules: [{ required: true }],
}] as any;
const xFormWrapper = {
formMap: [
{
key: 'id',
label: '工单ID',
type: 'input_number',
attrs: { disabled: true },
},
{
key: 'opinion',
label: '审批意见',
type: 'text_area',
rules: [{
required: status === 2,
validator: (rule: any, value: string, callback: any) => {
if (status === 2) {
value = value.trim();
const regexp = /^[ ]+$/;
if (value.length <= 0 || regexp.test(value)) {
callback('审批意见不能为空');
return false;
}
}
return true;
},
}],
}],
formData: { id },
okText: status === 1 ? '通过' : '驳回',
visible: true,
width: type === 0 ? 760 : 520,
title: renderModalTilte(type, status) as any,
onSubmit: (value: any) => {
value.id = id;
value.status = status;
if (value.brokerIdList) {
value.brokerIdList = value.brokerIdList && Array.isArray(value.brokerIdList) ?
value.brokerIdList : value.brokerIdList.split(',');
}
let params = {} as any;
if (type === 0) {
params = {
replicaNum: value.replicaNum ? Number(value.replicaNum) : '',
partitionNum: value.partitionNum ? Number(value.partitionNum) : null,
retentionTime: value.retentionTime,
brokerIdList: value.brokerIdList,
regionId: value.regionId,
};
params.regionId ? delete params.brokerIdList : delete params.regionId;
} else if (type === 2 || type === 12) {
params = {
partitionNum: value.partitionNum ? Number(value.partitionNum) : null,
brokerIdList: value.brokerIdList,
};
} else {
params = {};
}
const orderParams = {
id,
opinion: value.opinion.trim(),
status,
detail: JSON.stringify(params),
} as IApprovalOrder;
order.approvalOrder(orderParams).then(() => {
message.success('操作成功');
if (from) {
order.getOrderDetail(id);
}
order.getApplyOrderList(0);
order.getApprovalList(0);
}).catch(err => {
if (!err || !err.code) {
notification.error({
message: '错误',
description: '网络或服务器错误,请重试!',
});
}
});
},
};
if (type === 0 && status === 1) { // 通过 topic
xFormWrapper.formMap.splice(1, 0, ...formMap);
}
if ((type === 2 || type === 12) && status === 1) { // 通过配额 12分区
xFormWrapper.formMap.splice(1, 0, ...quotaFormMap);
}
wrapper.open(xFormWrapper);
};
export const renderOrderOpModal = (selectedRowKeys: IBaseOrder[], status: number) => {
const orderIdList = selectedRowKeys.map((ele: IBaseOrder) => {
return ele.id;
});
const xFormWrapper = {
formMap: [
{
key: 'opinion',
label: '审批意见',
type: 'text_area',
rules: [{
required: true,
validator: (rule: any, value: string, callback: any) => {
value = value.trim();
const regexp = /^[ ]+$/;
if (value.length <= 0 || regexp.test(value)) {
callback('审批意见不能为空');
return false;
}
return true;
},
}],
},
],
formData: {},
visible: true,
okText: status === 1 ? '通过' : '驳回',
title: status === 1 ? '通过' : '驳回',
onSubmit: async (value: any) => {
const params = {
opinion: value.opinion,
orderIdList,
status,
};
order.batchApprovalOrders(params).then(data => {
modal.setAction('close');
modal.showOrderOpResult();
});
},
};
wrapper.open(xFormWrapper);
};
export const RenderOrderOpResult = () => {
const handleOk = () => {
order.getApplyOrderList(0);
order.getApprovalList(0);
modal.close();
};
const flowColumns = [
{
title: '工单Id',
dataIndex: 'id',
key: 'id',
},
{
title: '状态',
dataIndex: 'code',
key: 'code',
render: (t: number) => {
return (
<span className={t === 0 ? 'success' : 'fail'}>
{t === 0 ? '成功' : '失败'}
</span>
);
},
},
{
title: '原因',
dataIndex: 'message',
key: 'message',
onCell: () => ({
style: {
maxWidth: 120,
...cellStyle,
},
}),
render: (text: string) => {
return (
<Tooltip placement="bottomLeft" title={text} >
{text}
</Tooltip>);
},
},
];
return (
<>
<Modal
title="审批结果"
visible={true}
onOk={handleOk}
onCancel={handleOk}
>
<Table
columns={flowColumns}
rowKey="id"
dataSource={order.batchApprovalList}
scroll={{ x: 450, y: 260 }}
pagination={false}
/>
</Modal>
</>
);
};

View File

@@ -0,0 +1,81 @@
import * as React from 'react';
import { Table, Modal, Tooltip, Icon, message, notification } from 'component/antd';
import { getApplyOnlineColumns } from 'container/topic/config';
import { observer } from 'mobx-react';
import { modal } from 'store/modal';
import { users } from 'store/users';
import { urlPrefix } from 'constants/left-menu';
import { region } from 'store';
import { topic } from 'store/topic';
@observer
export class RenderOrderOpResult extends React.Component {
public componentDidMount() {
topic.getConnectionInfo(modal.params.clusterId, modal.params.topicName, modal.params.appId);
}
public handleCancel = () => {
topic.setConnectionInfo([]);
modal.close();
}
public handleSubmit = () => {
const connectionList = topic.connectionInfo;
if (connectionList && connectionList.length) {
return message.warning('存在连接信息,无法申请下线!');
}
const online = {
clusterId: modal.params.clusterId,
topicName: modal.params.topicName,
};
const offlineParams = {
type: 10,
applicant: users.currentUser.username,
description: '',
extensions: JSON.stringify(online),
};
topic.applyTopicOnline(offlineParams).then((data: any) => {
notification.success({ message: '申请下线成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
modal.close();
}
public render() {
return (
<>
<Modal
visible={true}
className="stream-debug-modal"
title={
<span>
线
<Tooltip placement="right" title={'当前Topic存在消费的AppID请联系相关AppID负责人取消权限才能继续下线'} >
<Icon className="question-icon" type="question-circle" />
</Tooltip>
</span>
}
maskClosable={false}
onCancel={this.handleCancel}
onOk={this.handleSubmit}
okText="下线"
cancelText="取消"
okButtonProps={{ disabled: topic.connectLoading || !!topic.connectionInfo.length }}
width={700}
>
<Table
rowKey="key"
title={() => '连接信息'}
loading={topic.connectLoading}
scroll={{ x: 450, y: 260 }}
dataSource={topic.connectionInfo}
columns={getApplyOnlineColumns()}
pagination={false}
bordered={true}
/>
</Modal>
</>
);
}
}

View File

@@ -0,0 +1,753 @@
import { wrapper } from 'store';
import { notification } from 'component/antd';
import { IQuotaModelItem, ITopic, ILimitsItem, IQuotaQuery } from 'types/base-type';
import { topic, IAppsIdInfo } from 'store/topic';
import { users } from 'store/users';
import { app } from 'store/app';
import { cluster } from 'store/cluster';
import { AppSelect } from 'container/app-select';
import { PeakFlowInput } from '../topic/peak-flow';
import { urlPrefix } from 'constants/left-menu';
import { transMBToB, transBToMB } from 'lib/utils';
import { region } from 'store';
import * as React from 'react';
import '../app/index.less';
import { modal } from 'store/modal';
import { TopicAppSelect } from '../topic/topic-app-select';
import Url from 'lib/url-parser';
import { expandRemarks, quotaRemarks } from 'constants/strategy';
export const applyTopic = () => {
const xFormModal = {
formMap: [
{
key: 'clusterId',
label: '所属集群:',
type: 'select',
options: cluster.clusterData,
rules: [{ required: true, message: '请选择' }],
attrs: {
placeholder: '请选择',
},
}, {
key: 'topicName',
label: 'Topic名称',
attrs: {
addonBefore: region.currentRegion === 'us' || region.currentRegion === 'ru' ? `${region.currentRegion}01_` : '',
},
rules: [
{ required: true },
{
pattern: /^[-\w]{3,128}$/,
message: '只能包含字母、数字、下划线_和短划线(-),长度限制在3-128字符之间',
},
],
},
{
key: 'appId',
label: '所属应用:',
type: 'custom',
defaultValue: '',
rules: [{ required: true, message: '请选择' }],
customFormItem: <AppSelect selectData={app.data} />,
}, {
key: 'peakBytesIn',
label: '峰值流量',
type: 'custom',
rules: [{
required: true,
pattern: /^[1-9]\d*$/,
validator: (rule: any, value: any, callback: any) => {
const regexp = /^[1-9]\d*$/;
if (value.length <= 0) {
callback('流量上限不能为空');
return false;
} else if (!regexp.test(value)) {
callback('流量上限只能填写大于0的正整数');
return false;
}
return true;
},
}],
customFormItem: <PeakFlowInput />,
},
{
key: 'description',
label: '申请原因',
type: 'text_area',
rules: [{ required: true, pattern: /^.{5,}.$/s, message: '5' }],
attrs: {
placeholder: `概要描述Topic的数据源, Topic数据的生产者/消费者, Topic的申请原因及备注信息等。最多100个字
例如:
数据源xxx
生产消费方xxx
申请原因及备注xxx`,
rows: 7,
},
},
],
formData: {},
visible: true,
title: '申请Topic',
okText: '确认',
onSubmit: (value: any) => {
value.topicName = region.currentRegion === 'us' || region.currentRegion === 'ru' ?
`${region.currentRegion}01_` + value.topicName : value.topicName;
value.peakBytesIn = transMBToB(value.peakBytesIn);
const params = JSON.parse(JSON.stringify(value));
delete (params.description);
const quotaParams = {
type: 0,
applicant: users.currentUser.username,
description: value.description,
extensions: JSON.stringify(params),
};
topic.applyTopic(quotaParams).then(data => {
notification.success({ message: '申请Topic成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
},
};
wrapper.open(xFormModal);
};
export const deferTopic = (item: ITopic) => {
const xFormModal = {
formMap: [
{
key: 'topicName',
label: 'Topic名称',
value: '',
rules: [{ required: false, disabled: true }],
attrs: {
disabled: true,
},
},
{
key: 'retainDays',
label: '延期时间',
type: 'select',
value: '',
options: [{
label: '一周',
value: '7',
}, {
label: '一月',
value: '30',
}, {
label: '三月',
value: '90',
}],
rules: [{ required: false }],
attrs: { placeholder: '请选择延期时间' },
},
],
formData: {
topicName: item.topicName,
retainDays: '',
},
visible: true,
title: '申请延期',
okText: '确认',
onSubmit: (value: any) => {
value.clusterId = item.clusterId;
topic.deferTopic(value).then(data => {
notification.success({ message: '申请延期成功' });
});
},
};
wrapper.open(xFormModal);
};
export const applyOnlineModal = (item: ITopic) => {
modal.showOfflineTopicModal(item);
};
export const showApplyQuatoModal = (item: ITopic | IAppsIdInfo, record: IQuotaQuery) => {
const isProduce = item.access === 0 || item.access === 1;
const isConsume = item.access === 0 || item.access === 2;
const xFormModal = {
formMap: [
{
key: 'clusterName',
label: '集群名称',
rules: [{ required: true, message: '' }],
attrs: { disabled: true },
invisible: !item.hasOwnProperty('clusterName'),
}, {
key: 'topicName',
label: 'Topic名称',
rules: [{ required: true, message: '' }],
attrs: { disabled: true },
}, {
key: 'appId',
label: '所属应用:',
defaultValue: '',
rules: [{ required: true, message: '请输入' }],
attrs: { disabled: true },
}, {
key: 'produceQuota',
label: '发送数据速率',
attrs: {
disabled: isProduce,
placeholder: '请输入',
suffix: 'MB/s',
},
rules: [{
required: !isProduce,
message: '请输入',
}],
}, {
key: 'consumeQuota',
label: '消费数据速率',
attrs: {
disabled: isConsume,
placeholder: '请输入',
suffix: 'MB/s',
},
rules: [{
required: !isConsume,
message: '请输入',
}],
}, {
key: 'description',
label: '申请原因',
type: 'text_area',
rules: [{ required: true, pattern: /^.{5,}.$/, message: quotaRemarks }],
attrs: {
placeholder: quotaRemarks,
},
}],
formData: {
clusterName: item.clusterName,
topicName: record.topicName || item.topicName,
appId: record.appId || item.appId,
produceQuota: transBToMB(record.produceQuota),
consumeQuota: transBToMB(record.consumeQuota),
},
okText: '确认',
visible: true,
title: '申请配额',
onSubmit: (value: any) => {
const quota = {} as IQuotaModelItem;
Object.assign(quota, {
clusterId: record.clusterId || item.clusterId,
topicName: record.topicName || item.topicName,
appId: record.appId || item.appId,
consumeQuota: transMBToB(value.consumeQuota),
produceQuota: transMBToB(value.produceQuota),
});
if (item.isPhysicalClusterId) {
Object.assign(quota, {
isPhysicalClusterId: true,
});
}
const quotaParams = {
type: 2,
applicant: users.currentUser.username,
description: value.description,
extensions: JSON.stringify(quota),
};
topic.applyQuota(quotaParams).then((data) => {
notification.success({ message: '申请配额成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
},
};
wrapper.open(xFormModal);
};
let permission: number = null;
const updateFormModal = (appId: string) => {
const formMap = wrapper.xFormWrapper.formMap;
const formData = wrapper.xFormWrapper.formData;
const quota = app.appQuota.filter(ele => ele.appId === appId);
permission = quota[0].access;
const isProduce = quota[0].access === 0 || quota[0].access === 1;
const isConsume = quota[0].access === 0 || quota[0].access === 2;
formData.produceQuota = transBToMB(quota[0].produceQuota);
formData.consumeQuota = transBToMB(quota[0].consumerQuota);
formMap[3].attrs = { disabled: isProduce, suffix: 'MB/s' };
formMap[3].rules = [{ required: !isProduce, message: '请输入' }];
formMap[4].attrs = { disabled: isConsume, suffix: 'MB/s' };
formMap[4].rules = [{ required: !isConsume, message: '请输入' }];
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, formData);
};
export const showTopicApplyQuatoModal = (item: ITopic) => {
const xFormModal = {
formMap: [
{
key: 'clusterName',
label: '集群名称',
rules: [{ required: true, message: '' }],
attrs: { disabled: true },
invisible: !item.hasOwnProperty('clusterName'),
}, {
key: 'topicName',
label: 'Topic名称',
rules: [{ required: true, message: '' }],
attrs: { disabled: true },
}, {
key: 'appId',
label: '所属应用:',
defaultValue: '',
rules: [{
required: true,
validator: (rule: any, value: string, callback: any) => {
if (!value) {
callback('请选择应用');
return false;
}
if (permission === 0) {
callback('该应用无当前topic权限请选择其他应用或申请权限。');
return false;
}
return true;
},
}],
type: 'select',
options: app.appQuota,
attrs: {
onChange(value: string) {
updateFormModal(value);
},
},
}, { // 0 无权限 1可读 2可写 3 可读写 4可读写可管理
key: 'produceQuota',
label: '发送数据速率',
attrs: {
disabled: false,
placeholder: '请输入',
suffix: 'MB/s',
},
rules: [{
required: true,
message: '请输入',
}],
}, {
key: 'consumeQuota',
label: '消费数据速率',
attrs: {
disabled: false,
placeholder: '请输入',
suffix: 'MB/s',
},
rules: [{
required: true,
message: '请输入',
}],
}, {
key: 'description',
label: '申请原因',
type: 'text_area',
rules: [{ required: true, pattern: /^.{5,}$/, message: quotaRemarks }],
attrs: {
placeholder: quotaRemarks,
},
}],
formData: {
clusterName: item.clusterName,
topicName: item.topicName,
},
okText: '确认',
visible: true,
title: '申请配额',
onSubmit: (value: any) => {
const quota = {} as IQuotaModelItem;
Object.assign(quota, {
clusterId: item.clusterId,
topicName: item.topicName,
appId: value.appId,
consumeQuota: transMBToB(value.consumeQuota),
produceQuota: transMBToB(value.produceQuota),
});
const quotaParams = {
type: 2,
applicant: users.currentUser.username,
description: value.description,
extensions: JSON.stringify(quota),
};
topic.applyQuota(quotaParams).then((data) => {
notification.success({ message: '申请配额成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
},
};
wrapper.open(xFormModal);
};
export const updateAllTopicFormModal = () => {
const formMap = wrapper.xFormWrapper.formMap;
if (topic.authorities) {
const { consume, send, checkStatus } = judgeAccessStatus(topic.authorities.access);
formMap[3].defaultValue = checkStatus;
formMap[3].options = [{
label: `消费权限${consume ? '(已拥有)' : ''}`,
value: '1',
disabled: consume,
}, {
label: `发送权限${send ? '(已拥有)' : ''}`,
value: '2',
disabled: send,
}];
formMap[3].rules = [{
required: true,
validator: (rule: any, value: any, callback: any) => getPowerValidator(rule, value, callback, checkStatus, 'allTopic'),
}];
}
// tslint:disable-next-line:no-unused-expression
wrapper.ref && wrapper.ref.updateFormMap$(formMap, wrapper.xFormWrapper.formData, true, ['access', 'description']);
};
const getPowerValidator = (rule: any, value: any, callback: any, checkStatus: any, isAll?: any) => {
if (
(!checkStatus.length && !value.length) ||
(checkStatus.indexOf('1') !== -1 || checkStatus.indexOf('2') !== -1) && value.length === 1
) {
callback('请选择权限!');
return false;
}
if (isAll && checkStatus.length === 2) {
callback('您已拥有发送,消费权限!');
return false;
}
return true;
};
const getCheckStatus = (checkStatus: string[], accessValue: string) => {
let access = null as string;
if (!checkStatus.length) {
access = accessValue.length === 2 ? '3' : accessValue[0];
} else if (checkStatus.indexOf('1') !== -1 && checkStatus.length === 1) {
access = '2';
} else if (checkStatus.indexOf('2') !== -1 && checkStatus.length === 1) {
access = '1';
}
return access;
};
const judgeAccessStatus = (access: number) => {
const consume = access === 1 || access === 3 || access === 4;
const send = access === 2 || access === 3 || access === 4;
const checkStatus = access === 0 ? [] : (access === 1 || access === 2) ? [access + ''] : ['1', '2'];
return { consume, send, checkStatus };
};
export const showAllPermissionModal = (item: ITopic) => {
let appId: string = null;
if (!app.data || !app.data.length) {
return notification.info({
message: (
<>
<span>
<a href={`${urlPrefix}/topic/app-list?application=1`}></a>
</span>
</>),
});
}
const index = app.data.findIndex(row => row.appId === item.appId);
appId = index > -1 ? item.appId : app.data[0].appId;
topic.getAuthorities(appId, item.clusterId, item.topicName).then((data) => {
showAllPermission(appId, item, data.access);
});
};
const showAllPermission = (appId: string, item: ITopic, access: number) => {
const { consume, send, checkStatus } = judgeAccessStatus(access);
const xFormModal = {
formMap: [
{
key: 'topicName',
label: 'Topic名称',
defaultValue: item.topicName,
rules: [{ required: true, message: '请输入Topic名称' }],
attrs: {
placeholder: '请输入Topic名称',
disabled: true,
},
},
{
key: 'clusterName',
label: '集群名称',
defaultValue: item.clusterName,
rules: [{ required: true, message: '请输入集群名称' }],
attrs: {
placeholder: '请输入集群名称',
disabled: true,
},
},
{
key: 'appId',
label: '绑定应用',
defaultValue: appId,
rules: [{ required: true, message: '请选择应用' }],
type: 'custom',
customFormItem: <TopicAppSelect selectData={app.data} parameter={item} />,
},
{
key: 'access',
label: '权限',
type: 'check_box',
defaultValue: checkStatus,
options: [{
label: `消费权限${consume ? '(已拥有)' : ''}`,
value: '1',
disabled: consume,
}, {
label: `发送权限${send ? '(已拥有)' : ''}`,
value: '2',
disabled: send,
}],
rules: [{
required: true,
validator: (rule: any, value: any, callback: any) => getPowerValidator(rule, value, callback, checkStatus, 'allTopic'),
}],
},
{
key: 'description',
label: '申请原因',
type: 'text_area',
rules: [{
required: true,
validator: (rule: any, value: string, callback: any) => {
const regexp = /^.{5,}.$/;
value = value.trim();
if (!regexp.test(value)) {
callback('请输入至少5个字符');
return false;
}
return true;
},
}],
attrs: {
placeholder: '请输入至少5个字符',
},
},
],
formData: {},
visible: true,
title: '申请权限',
okText: '确认',
onSubmit: (value: ILimitsItem) => {
const { checkStatus: originStatus } = judgeAccessStatus(topic.authorities.access);
const access = getCheckStatus(originStatus, value.access);
const params = {} as ILimitsItem;
Object.assign(params, { clusterId: item.clusterId, topicName: item.topicName, appId: value.appId, access });
const accessParams = {
type: 3,
applicant: users.currentUser.username,
description: value.description.trim(),
extensions: JSON.stringify(params),
};
topic.applyQuota(accessParams).then(data => {
notification.success({ message: '申请权限成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
}).catch((err) => {
notification.error({ message: '申请权限失败' });
});
},
};
wrapper.open(xFormModal);
};
export const showPermissionModal = (item: ITopic) => {
const { consume, send, checkStatus } = judgeAccessStatus(item.access);
const xFormModal = {
formMap: [
{
key: 'topicName',
label: 'Topic名称',
defaultValue: item.topicName,
rules: [{ required: true, message: '请输入Topic名称' }],
attrs: {
placeholder: '请输入Topic名称',
disabled: true,
},
},
{
key: 'clusterName',
label: '集群名称',
defaultValue: item.clusterName,
rules: [{ required: true, message: '请输入集群名称' }],
attrs: {
placeholder: '请输入集群名称',
disabled: true,
},
},
{
key: 'appName',
label: '绑定应用',
defaultValue: `${item.appName}${item.appId}`,
rules: [{ required: true, message: '请选择应用' }],
attrs: {
disabled: true,
},
},
{
key: 'access',
label: '权限',
type: 'check_box',
defaultValue: checkStatus,
options: [{
label: `消费权限${consume ? '(已拥有)' : ''}`,
value: '1',
disabled: consume,
}, {
label: `发送权限${send ? '(已拥有)' : ''}`,
value: '2',
disabled: send,
}],
rules: [{
required: true,
validator: (rule: any, value: any, callback: any) => getPowerValidator(rule, value, callback, checkStatus),
}],
},
{
key: 'description',
label: '申请原因',
type: 'text_area',
rules: [{
required: true,
validator: (rule: any, value: string, callback: any) => {
const regexp = /^.{5,}.$/;
value = value.trim();
if (!regexp.test(value)) {
callback('请输入至少5个字符');
return false;
}
return true;
},
}],
attrs: {
placeholder: '请输入至少5个字符',
},
},
],
formData: {},
visible: true,
title: '申请权限',
okText: '确认',
onSubmit: (value: ILimitsItem) => {
const access = getCheckStatus(checkStatus, value.access);
const params = {} as ILimitsItem;
Object.assign(params, { clusterId: item.clusterId, topicName: item.topicName, appId: item.appId, access });
const accessParams = {
type: 3,
applicant: users.currentUser.username,
description: value.description.trim(),
extensions: JSON.stringify(params),
};
topic.applyQuota(accessParams).then(data => {
notification.success({ message: '申请权限成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
});
},
};
wrapper.open(xFormModal);
};
export const showTopicEditModal = (item: ITopic) => {
const xFormModal = {
formMap: [
{
key: 'topicName',
label: 'Topic名称',
attrs: { disabled: true },
rules: [{ required: false }],
}, {
key: 'description',
label: '备注',
type: 'text_area',
rules: [{ required: false }, { pattern: /^.{5,}.$/, message: '请输入至少5个字符' }],
},
],
formData: {
topicName: item.topicName,
description: item.description,
},
visible: true,
title: '编辑',
onSubmit: (value: any) => {
const params = {} as IQuotaModelItem;
Object.assign(params,
{
clusterId: item.clusterId,
topicName: value.topicName,
description: value.description,
});
topic.updateTopic(params).then(() => {
notification.success({ message: '编辑成功' });
topic.getTopic();
topic.getExpired();
});
},
};
wrapper.open(xFormModal);
};
export const applyExpandModal = (item: ITopic) => {
const xFormModal = {
formMap: [
{
key: 'topicName',
label: 'Topic名称',
attrs: { disabled: true },
rules: [{ required: true }],
}, {
key: 'needIncrPartitionNum',
label: '分区',
type: 'input_number',
rules: [{
required: true,
message: '请输入0-100正整数',
pattern: /^((?!0)\d{1,2}|100)$/,
}],
attrs: { placeholder: '0-100正整数' },
renderExtraElement: () => <div className="form-tip mr--10">3MB/s一个</div>,
}, {
key: 'description',
label: '申请原因',
type: 'text_area',
rules: [{ required: true, pattern: /^.{5,}.$/, message: expandRemarks }],
attrs: { placeholder: expandRemarks },
},
],
formData: {
topicName: item.topicName,
description: item.description,
},
visible: true,
title: '申请分区',
customRenderElement: <div className="expand-text">Topic已被限流</div>,
onSubmit: (value: any) => {
const isPhysicalClusterId = Url().search.hasOwnProperty('isPhysicalClusterId') && Url().search.isPhysicalClusterId;
const offlineParams = {
type: 12,
applicant: users.currentUser.username,
description: value.description,
extensions: JSON.stringify({
clusterId: item.clusterId,
topicName: item.topicName,
isPhysicalClusterId,
needIncrPartitionNum: value.needIncrPartitionNum,
}),
};
app.applyExpand(offlineParams).then((data: any) => {
notification.success({ message: '申请分区成功' });
window.location.href = `${urlPrefix}/user/order-detail/?orderId=${data.id}&region=${region.currentRegion}`;
}).catch((err) => {
notification.error({ message: '申请权限失败' });
});
},
};
wrapper.open(xFormModal);
};