mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-09 08:27:47 +08:00
kafka-manager 2.0
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { XFormComponent } from 'component/x-form';
|
||||
import { xActionFormMap } from './config';
|
||||
import * as React from 'react';
|
||||
import { IRequestParams, IStrategyAction, IConfigForm } from 'types/alarm';
|
||||
|
||||
export class ActionForm extends React.Component {
|
||||
public $form: any = null;
|
||||
|
||||
public getFormData() {
|
||||
let configValue = null as IConfigForm;
|
||||
this.$form.validateFields((error: Error, result: IConfigForm) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
configValue = result;
|
||||
});
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public resetFormData() {
|
||||
this.$form.resetFields();
|
||||
}
|
||||
|
||||
public updateFormData(monitorRule: IRequestParams) {
|
||||
const strategyAction = monitorRule.strategyActionList[0] || {} as IStrategyAction;
|
||||
this.$form.setFieldsValue({
|
||||
level: monitorRule.priority,
|
||||
alarmPeriod: strategyAction.converge.split(',')[0],
|
||||
alarmTimes: strategyAction.converge.split(',')[1],
|
||||
acceptGroup: strategyAction.notifyGroup,
|
||||
callback: strategyAction.callback,
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
const formData = {};
|
||||
const formLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 12 },
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="config-wrapper">
|
||||
<span className="span-tag">配置发送信息</span>
|
||||
<div className="alarm-x-form action-form">
|
||||
<XFormComponent
|
||||
ref={form => this.$form = form}
|
||||
formData={formData}
|
||||
formMap={xActionFormMap}
|
||||
formLayout={formLayout}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import * as React from 'react';
|
||||
import { alarm } from 'store/alarm';
|
||||
import { IMonitorGroups } from 'types/base-type';
|
||||
import { getValueFromLocalStorage, setValueToLocalStorage } from 'lib/local-storage';
|
||||
import { VirtualScrollSelect } from '../../../component/virtual-scroll-select';
|
||||
|
||||
interface IAlarmSelectProps {
|
||||
onChange?: (result: string[]) => any;
|
||||
value?: string[];
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
export class AlarmSelect extends React.Component<IAlarmSelectProps> {
|
||||
public getData = async () => {
|
||||
const originMonitorList = getValueFromLocalStorage('monitorGroups');
|
||||
if (originMonitorList) return originMonitorList;
|
||||
return await this.fetchMonitor();
|
||||
}
|
||||
|
||||
public fetchMonitor = async () => {
|
||||
let data = await alarm.getMonitorGroups();
|
||||
data = (data || []).map((item: IMonitorGroups) => {
|
||||
return {
|
||||
...item,
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
};
|
||||
});
|
||||
setValueToLocalStorage('monitorGroups', data);
|
||||
return data;
|
||||
}
|
||||
public handleChange = (params: string[]) => {
|
||||
const { onChange } = this.props;
|
||||
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
onChange && onChange(params);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { value, isDisabled } = this.props;
|
||||
return (
|
||||
<>
|
||||
<VirtualScrollSelect
|
||||
attrs={{ mode: 'multiple', placeholder: '请选择报警接收组' }}
|
||||
value={value}
|
||||
isDisabled={isDisabled}
|
||||
getData={this.getData}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<a
|
||||
className="icon-color"
|
||||
target="_blank"
|
||||
href="https://github.com/didi/kafka-manager"
|
||||
>
|
||||
新建告警组?
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
246
kafka-manager-console/src/container/alarm/add-alarm/config.tsx
Normal file
246
kafka-manager-console/src/container/alarm/add-alarm/config.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
import * as React from 'react';
|
||||
import { Tooltip, notification, Radio, Icon, Popconfirm, RadioChangeEvent } from 'component/antd';
|
||||
import { IMonitorStrategies, ILabelValue } from 'types/base-type';
|
||||
import { IFormItem, IFormSelect } from 'component/x-form';
|
||||
import { AlarmSelect } from 'container/alarm/add-alarm/alarm-select';
|
||||
import { weekOptions } from 'constants/status-map';
|
||||
import { alarm } from 'store/alarm';
|
||||
import { app } from 'store/app';
|
||||
import moment from 'moment';
|
||||
import { cellStyle } from 'constants/table';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
import { region } from 'store/region';
|
||||
|
||||
export const getAlarmColumns = (urlPrefix: string) => {
|
||||
const columns = [
|
||||
{
|
||||
title: '告警名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '25%',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 250,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
sorter: (a: IMonitorStrategies, b: IMonitorStrategies) => a.name.charCodeAt(0) - b.name.charCodeAt(0),
|
||||
render: (text: string, record: IMonitorStrategies) => {
|
||||
return (
|
||||
<Tooltip placement="bottomLeft" title={record.name} >
|
||||
<a href={`${urlPrefix}/alarm/alarm-detail?id=${record.id}®ion=${region.currentRegion}`}> {text} </a>
|
||||
</Tooltip>);
|
||||
},
|
||||
}, {
|
||||
title: '应用名称',
|
||||
dataIndex: 'appName',
|
||||
key: 'appName',
|
||||
width: '25%',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 250,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
sorter: (a: IMonitorStrategies, b: IMonitorStrategies) => a.appName.charCodeAt(0) - b.appName.charCodeAt(0),
|
||||
render: (text: string, record: IMonitorStrategies) =>
|
||||
<Tooltip placement="bottomLeft" title={record.principals} >{text}</Tooltip>,
|
||||
}, {
|
||||
title: '操作人',
|
||||
dataIndex: 'operator',
|
||||
key: 'operator',
|
||||
width: '20%',
|
||||
onCell: () => ({
|
||||
style: {
|
||||
maxWidth: 100,
|
||||
...cellStyle,
|
||||
},
|
||||
}),
|
||||
sorter: (a: IMonitorStrategies, b: IMonitorStrategies) => a.operator.charCodeAt(0) - b.operator.charCodeAt(0),
|
||||
render: (text: string) => <Tooltip placement="bottomLeft" title={text} >{text}</Tooltip>,
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: '20%',
|
||||
sorter: (a: IMonitorStrategies, b: IMonitorStrategies) => b.createTime - a.createTime,
|
||||
render: (time: number) => moment(time).format(timeFormat),
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
key: 'operation',
|
||||
width: '10%',
|
||||
render: (text: string, item: IMonitorStrategies) => (
|
||||
<>
|
||||
<a href={`${urlPrefix}/alarm/modify?id=${item.id}`} className="action-button">修改</a>
|
||||
<Popconfirm
|
||||
title="确定删除?"
|
||||
onConfirm={() => deteleMonitor(item)}
|
||||
>
|
||||
<a>删除</a>
|
||||
</Popconfirm>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
|
||||
export const getRandomKey = () => {
|
||||
return (new Date()).getTime();
|
||||
};
|
||||
|
||||
export const deteleMonitor = (item: IMonitorStrategies) => {
|
||||
alarm.deteleMonitorStrategies(item.id).then(data => {
|
||||
notification.success({ message: '删除成功' });
|
||||
});
|
||||
};
|
||||
|
||||
export const getAlarmTime = () => {
|
||||
const timeOptions = [] as ILabelValue[];
|
||||
const defaultTime = [] as number[];
|
||||
|
||||
for (let i = 0; i < 24; i++) {
|
||||
timeOptions.push({
|
||||
label: `${i}点`,
|
||||
value: i,
|
||||
});
|
||||
defaultTime.push(i);
|
||||
}
|
||||
return { timeOptions, defaultTime };
|
||||
};
|
||||
export const getAlarmWeek = () => {
|
||||
const defWeek = [] as number[];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
defWeek.push(i);
|
||||
}
|
||||
return { defWeek, weekOptions };
|
||||
};
|
||||
|
||||
interface IRadioProps {
|
||||
onChange?: (result: number) => any;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
const isDetailPage = window.location.pathname.includes('/alarm-detail'); // 判断是否为详情
|
||||
|
||||
class RadioIcon extends React.Component<IRadioProps> {
|
||||
public onRadioChange = (e: RadioChangeEvent) => {
|
||||
const { onChange } = this.props;
|
||||
if (onChange) {
|
||||
onChange(e.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { value } = this.props;
|
||||
return (
|
||||
<Radio.Group
|
||||
name="radiogroup"
|
||||
value={value || 3}
|
||||
disabled={isDetailPage}
|
||||
onChange={this.onRadioChange}
|
||||
>
|
||||
<Radio value={1} key={1}>
|
||||
一级告警
|
||||
<Icon type="phone" />
|
||||
<Icon type="message" />
|
||||
<Icon type="mail" />
|
||||
<Icon type="dingding" />
|
||||
</Radio>
|
||||
<Radio value={2} key={2}>
|
||||
二级告警
|
||||
<Icon type="message" />
|
||||
<Icon type="mail" />
|
||||
<Icon type="dingding" />
|
||||
</Radio>
|
||||
<Radio value={3} key={3}>
|
||||
三级告警
|
||||
<Icon type="mail" />
|
||||
<Icon type="dingding" />
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const xActionFormMap = [{
|
||||
key: 'level',
|
||||
label: '报警级别',
|
||||
type: 'custom',
|
||||
defaultValue: 3,
|
||||
customFormItem: <RadioIcon />,
|
||||
rules: [{ required: true, message: '请输入报警接收组' }],
|
||||
}, {
|
||||
key: 'alarmPeriod',
|
||||
label: '报警周期(分钟)',
|
||||
type: 'input_number',
|
||||
attrs: {
|
||||
min: 0,
|
||||
disabled: isDetailPage,
|
||||
},
|
||||
rules: [{ required: true, message: '请输入报警周期' }],
|
||||
}, {
|
||||
key: 'alarmTimes',
|
||||
label: '周期内报警次数',
|
||||
type: 'input_number',
|
||||
attrs: {
|
||||
min: 0,
|
||||
disabled: isDetailPage,
|
||||
},
|
||||
rules: [{ required: true, message: '请输入周期内报警次数' }],
|
||||
}, {
|
||||
key: 'acceptGroup',
|
||||
label: '报警接收组',
|
||||
type: 'custom',
|
||||
customFormItem: <AlarmSelect isDisabled={isDetailPage}/>,
|
||||
rules: [{ required: true, message: '请输入报警接收组' }],
|
||||
},
|
||||
{
|
||||
key: 'callback',
|
||||
label: '回调地址',
|
||||
rules: [{ required: false, message: '请输入回调地址' }],
|
||||
attrs: {disabled: isDetailPage},
|
||||
}] as unknown as IFormSelect[]; // as IFormItem[];
|
||||
|
||||
export const xTypeFormMap = [{
|
||||
key: 'alarmName',
|
||||
label: '告警名称',
|
||||
rules: [{ required: true, message: '请输入告警名称' }],
|
||||
attrs: {placeholder: '请输入', disabled: isDetailPage},
|
||||
}, {
|
||||
key: 'app',
|
||||
label: '所属应用',
|
||||
type: 'select',
|
||||
attrs: {
|
||||
placeholder: '请选择',
|
||||
optionFilterProp: 'children',
|
||||
showSearch: true,
|
||||
filterOption: (input: any, option: any) => {
|
||||
if ( typeof option.props.children === 'object' ) {
|
||||
const { props } = option.props.children as any;
|
||||
return (props.children + '').toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
}
|
||||
return (option.props.children + '').toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
},
|
||||
onChange: (e: string) => app.changeActiveApp(e),
|
||||
disabled: isDetailPage,
|
||||
},
|
||||
rules: [{ required: true, message: '请输入报警接收组' }],
|
||||
}] as unknown as IFormSelect[];
|
||||
|
||||
export const xTimeFormMap = [{
|
||||
key: 'weeks',
|
||||
label: '每周',
|
||||
type: 'check_box',
|
||||
defaultValue: getAlarmWeek().defWeek,
|
||||
options: getAlarmWeek().weekOptions,
|
||||
rules: [{ required: true, message: '请选择' }],
|
||||
}, {
|
||||
key: 'hours',
|
||||
label: '每天',
|
||||
type: 'check_box',
|
||||
defaultValue: getAlarmTime().defaultTime,
|
||||
options: getAlarmTime().timeOptions,
|
||||
rules: [{ required: true, message: '请选择' }],
|
||||
}] as unknown as IFormSelect[];
|
||||
@@ -0,0 +1,391 @@
|
||||
import * as React from 'react';
|
||||
import { Select, Spin, Form, Tooltip } from 'component/antd';
|
||||
import { message } from 'antd';
|
||||
import { IFormItem } from 'component/x-form';
|
||||
import { cluster } from 'store/cluster';
|
||||
import { alarm } from 'store/alarm';
|
||||
import { topic } from 'store/topic';
|
||||
import { observer } from 'mobx-react';
|
||||
import { IRequestParams, IStrategyFilter } from 'types/alarm';
|
||||
import { filterKeys } from 'constants/strategy';
|
||||
import { VirtualScrollSelect } from 'component/virtual-scroll-select';
|
||||
import { IsNotNaN } from 'lib/utils';
|
||||
import { searchProps } from 'constants/table';
|
||||
|
||||
interface IDynamicProps {
|
||||
form?: any;
|
||||
formData?: any;
|
||||
}
|
||||
|
||||
interface IFormSelect extends IFormItem {
|
||||
options: Array<{ key?: string | number, value: string | number, label: string }>;
|
||||
}
|
||||
|
||||
interface IVritualScrollSelect extends IFormSelect {
|
||||
getData: () => any;
|
||||
isDisabled: boolean;
|
||||
refetchData?: boolean;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DynamicSetFilter extends React.Component<IDynamicProps> {
|
||||
public isDetailPage = window.location.pathname.includes('/alarm-detail'); // 判断是否为详情
|
||||
public monitorType: string = null;
|
||||
public clusterId: number = null;
|
||||
public clusterName: string = null;
|
||||
public topicName: string = null;
|
||||
public consumerGroup: string = null;
|
||||
public location: string = null;
|
||||
|
||||
public getFormValidateData() {
|
||||
const filterList = [] as IStrategyFilter[];
|
||||
let monitorType = '' as string;
|
||||
let filterObj = {} as any;
|
||||
|
||||
this.props.form.validateFields((err: Error, values: any) => {
|
||||
if (!err) {
|
||||
monitorType = values.monitorType;
|
||||
const index = cluster.clusterData.findIndex(item => item.clusterId === values.cluster);
|
||||
if (index > -1) {
|
||||
values.clusterName = cluster.clusterData[index].clusterName;
|
||||
}
|
||||
for (const key of Object.keys(values)) {
|
||||
if (filterKeys.indexOf(key) > -1) { // 只有这几种值可以设置
|
||||
filterList.push({
|
||||
tkey: key === 'clusterName' ? 'cluster' : key, // 传参需要将clusterName转成cluster
|
||||
topt: '=',
|
||||
tval: [values[key]],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return filterObj = {
|
||||
monitorType,
|
||||
filterList,
|
||||
};
|
||||
}
|
||||
|
||||
public resetForm() {
|
||||
const { resetFields } = this.props.form;
|
||||
this.clearFormData();
|
||||
resetFields();
|
||||
}
|
||||
|
||||
public resetFormValue(
|
||||
monitorType: string = null,
|
||||
clusterId: number = null,
|
||||
topicName: string = null,
|
||||
consumerGroup: string = null,
|
||||
location: string = null) {
|
||||
const { setFieldsValue } = this.props.form;
|
||||
setFieldsValue({
|
||||
cluster: clusterId,
|
||||
topic: topicName,
|
||||
consumerGroup,
|
||||
location,
|
||||
monitorType,
|
||||
});
|
||||
}
|
||||
|
||||
public getClusterId = (clusterName: string) => {
|
||||
let clusterId = null;
|
||||
const index = cluster.clusterData.findIndex(item => item.clusterName === clusterName);
|
||||
if (index > -1) {
|
||||
clusterId = cluster.clusterData[index].clusterId;
|
||||
}
|
||||
if (clusterId) {
|
||||
cluster.getClusterMetaTopics(clusterId);
|
||||
this.clusterId = clusterId;
|
||||
return this.clusterId;
|
||||
}
|
||||
return this.clusterId = clusterName as any;
|
||||
}
|
||||
|
||||
public async initFormValue(monitorRule: IRequestParams) {
|
||||
const strategyFilterList = monitorRule.strategyFilterList;
|
||||
const clusterFilter = strategyFilterList.filter(item => item.tkey === 'cluster')[0];
|
||||
const topicFilter = strategyFilterList.filter(item => item.tkey === 'topic')[0];
|
||||
const consumerFilter = strategyFilterList.filter(item => item.tkey === 'consumerGroup')[0];
|
||||
|
||||
const clusterName = clusterFilter ? clusterFilter.tval[0] : null;
|
||||
const topic = topicFilter ? topicFilter.tval[0] : null;
|
||||
const consumerGroup = consumerFilter ? consumerFilter.tval[0] : null;
|
||||
const location: string = null;
|
||||
const monitorType = monitorRule.strategyExpressionList[0].metric;
|
||||
alarm.changeMonitorStrategyType(monitorType);
|
||||
|
||||
await this.getClusterId(clusterName);
|
||||
await this.handleSelectChange(topic, 'topic');
|
||||
await this.handleSelectChange(consumerGroup, 'consumerGroup');
|
||||
this.resetFormValue(monitorType, this.clusterId, topic, consumerGroup, location);
|
||||
}
|
||||
|
||||
public clearFormData() {
|
||||
this.monitorType = null;
|
||||
this.topicName = null;
|
||||
this.clusterId = null;
|
||||
this.consumerGroup = null;
|
||||
this.location = null;
|
||||
this.resetFormValue();
|
||||
}
|
||||
|
||||
public async handleClusterChange(e: number) {
|
||||
this.clusterId = e;
|
||||
this.topicName = null;
|
||||
topic.setLoading(true);
|
||||
await cluster.getClusterMetaTopics(e);
|
||||
this.resetFormValue(this.monitorType, e, null, this.consumerGroup, this.location);
|
||||
topic.setLoading(false);
|
||||
}
|
||||
|
||||
public handleSelectChange = (e: string, type: 'topic' | 'consumerGroup' | 'location') => {
|
||||
switch (type) {
|
||||
case 'topic':
|
||||
if (!this.clusterId) {
|
||||
return message.info('请选择集群');
|
||||
}
|
||||
this.topicName = e;
|
||||
const type = this.dealMonitorType();
|
||||
if (['kafka-consumer-maxLag', 'kafka-consumer-maxDelayTime', 'kafka-consumer-lag'].indexOf(type) > -1) {
|
||||
this.getConsumerInfo();
|
||||
}
|
||||
break;
|
||||
case 'consumerGroup':
|
||||
this.consumerGroup = e;
|
||||
break;
|
||||
case 'location':
|
||||
this.location = e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public getConsumerInfo = () => {
|
||||
if (!this.clusterId || !this.topicName) {
|
||||
return;
|
||||
}
|
||||
topic.setLoading(true);
|
||||
if (IsNotNaN(this.clusterId)) {
|
||||
topic.getConsumerGroups(this.clusterId, this.topicName);
|
||||
}
|
||||
this.consumerGroup = null;
|
||||
this.location = null;
|
||||
this.resetFormValue(this.monitorType, this.clusterId, this.topicName);
|
||||
topic.setLoading(false);
|
||||
}
|
||||
|
||||
public dealMonitorType() {
|
||||
const index = alarm.monitorType.indexOf('-');
|
||||
let type = alarm.monitorType;
|
||||
if (index > -1) {
|
||||
type = type.substring(index + 1);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public getRenderItem() {
|
||||
const type = this.dealMonitorType();
|
||||
const showMore = ['kafka-consumer-maxLag', 'kafka-consumer-maxDelayTime', 'kafka-consumer-lag'].indexOf(type) > -1;
|
||||
this.monitorType = alarm.monitorType;
|
||||
|
||||
const monitorType = {
|
||||
key: 'monitorType',
|
||||
label: '监控指标',
|
||||
type: 'select',
|
||||
options: alarm.monitorTypeList.map(item => ({
|
||||
label: item.metricName,
|
||||
value: item.metricName,
|
||||
})),
|
||||
attrs: {
|
||||
placeholder: '请选择',
|
||||
className: 'large-size',
|
||||
disabled: this.isDetailPage,
|
||||
optionFilterProp: 'children',
|
||||
showSearch: true,
|
||||
filterOption: (input: any, option: any) => {
|
||||
if (typeof option.props.children === 'object') {
|
||||
const { props } = option.props.children as any;
|
||||
return (props.children + '').toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
}
|
||||
return (option.props.children + '').toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
},
|
||||
onChange: (e: string) => this.handleTypeChange(e),
|
||||
},
|
||||
rules: [{ required: true, message: '请选择监控指标' }],
|
||||
} as IVritualScrollSelect;
|
||||
|
||||
const clusterItem = {
|
||||
label: '集群',
|
||||
options: cluster.clusterData,
|
||||
defaultValue: this.clusterId,
|
||||
rules: [{ required: true, message: '请选择集群' }],
|
||||
attrs: {
|
||||
placeholder: '请选择集群',
|
||||
className: 'middle-size',
|
||||
disabled: this.isDetailPage,
|
||||
onChange: (e: number) => this.handleClusterChange(e),
|
||||
},
|
||||
key: 'cluster',
|
||||
} as unknown as IVritualScrollSelect;
|
||||
|
||||
const topicItem = {
|
||||
label: 'Topic',
|
||||
defaultValue: this.topicName,
|
||||
rules: [{ required: true, message: '请选择Topic' }],
|
||||
isDisabled: this.isDetailPage,
|
||||
options: cluster.clusterMetaTopics.map(item => {
|
||||
return {
|
||||
label: item.topicName,
|
||||
value: item.topicName,
|
||||
};
|
||||
}),
|
||||
attrs: {
|
||||
placeholder: '请选择Topic',
|
||||
className: 'middle-size',
|
||||
disabled: this.isDetailPage,
|
||||
onChange: (e: string) => this.handleSelectChange(e, 'topic'),
|
||||
},
|
||||
key: 'topic',
|
||||
} as IVritualScrollSelect;
|
||||
|
||||
const consumerGroupItem = {
|
||||
label: '消费组',
|
||||
options: topic.consumerGroups.map(item => {
|
||||
return {
|
||||
label: item.consumerGroup,
|
||||
value: item.consumerGroup,
|
||||
};
|
||||
}),
|
||||
defaultValue: this.consumerGroup,
|
||||
rules: [{ required: showMore, message: '请选择消费组' }],
|
||||
attrs: {
|
||||
placeholder: '请选择消费组',
|
||||
className: 'middle-size',
|
||||
disabled: this.isDetailPage,
|
||||
onChange: (e: string) => this.handleSelectChange(e, 'consumerGroup'),
|
||||
},
|
||||
key: 'consumerGroup',
|
||||
} as IVritualScrollSelect;
|
||||
|
||||
const locationItem = {
|
||||
label: 'location',
|
||||
options: topic.filterGroups.map(item => {
|
||||
return {
|
||||
label: item.location,
|
||||
value: item.location,
|
||||
};
|
||||
}),
|
||||
defaultValue: this.location,
|
||||
rules: [{ required: showMore, message: '请选择location' }],
|
||||
attrs: {
|
||||
placeholder: '请选择location',
|
||||
optionFilterProp: 'children',
|
||||
showSearch: true,
|
||||
className: 'middle-size',
|
||||
disabled: this.isDetailPage,
|
||||
onChange: (e: string) => this.handleSelectChange(e, 'location'),
|
||||
},
|
||||
key: 'location',
|
||||
} as IVritualScrollSelect;
|
||||
|
||||
const common = (
|
||||
<>
|
||||
{this.renderFormItem(clusterItem)}
|
||||
{this.renderFormItem(topicItem)}
|
||||
</>
|
||||
);
|
||||
|
||||
const more = showMore ? (
|
||||
<>
|
||||
{this.renderFormItem(consumerGroupItem)}
|
||||
{/* {this.renderFormItem(locationItem)} */}
|
||||
</>
|
||||
) : null;
|
||||
return (
|
||||
<>
|
||||
<div className="dynamic-set">
|
||||
{this.renderFormItem(monitorType)}
|
||||
<ul>{common}{more}</ul>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public handleTypeChange = (e: string) => {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
this.clearFormData();
|
||||
alarm.changeMonitorStrategyType(e);
|
||||
}
|
||||
|
||||
public getSelectFormItem(item: IFormItem) {
|
||||
return (
|
||||
<Select
|
||||
key={item.key}
|
||||
{...item.attrs}
|
||||
{...searchProps}
|
||||
>
|
||||
{(item as IFormSelect).options && (item as IFormSelect).options.map((v, index) => (
|
||||
<Select.Option
|
||||
key={v.value || v.key || index}
|
||||
value={v.value}
|
||||
>
|
||||
{v.label.length > 25 ? <Tooltip placement="bottomLeft" title={v.label}>
|
||||
{v.label}
|
||||
</Tooltip> : v.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
public renderFormItem(item: IVritualScrollSelect, virtualScroll: boolean = false) {
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
const { formData = {} } = this.props;
|
||||
const initialValue = formData[item.key] === 0 ? 0 : (formData[item.key] || item.defaultValue || '');
|
||||
const getFieldValue = {
|
||||
initialValue,
|
||||
rules: item.rules || [{ required: true, message: '请填写' }],
|
||||
};
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 10 },
|
||||
};
|
||||
return (
|
||||
<Form.Item
|
||||
label={item.label}
|
||||
key={item.key}
|
||||
{...formItemLayout}
|
||||
>
|
||||
{getFieldDecorator(item.key, getFieldValue)(
|
||||
virtualScroll ?
|
||||
<VirtualScrollSelect
|
||||
attrs={item.attrs}
|
||||
isDisabled={item.isDisabled}
|
||||
onChange={item.attrs.onChange}
|
||||
getData={item.getData}
|
||||
refetchData={item.refetchData}
|
||||
/>
|
||||
: this.getSelectFormItem(item),
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
cluster.getClusters();
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Spin spinning={cluster.filterLoading}>
|
||||
<Form>
|
||||
<div className="form-list">
|
||||
{this.getRenderItem()}
|
||||
</div>
|
||||
</Form>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const WrappedDynamicSetFilter = Form.create({ name: 'dynamic_filter_form_item' })(DynamicSetFilter);
|
||||
224
kafka-manager-console/src/container/alarm/add-alarm/index.less
Normal file
224
kafka-manager-console/src/container/alarm/add-alarm/index.less
Normal file
@@ -0,0 +1,224 @@
|
||||
.btn-group {
|
||||
background: white;
|
||||
width: calc(100% - 215px);
|
||||
position: fixed;
|
||||
top: 75px;
|
||||
right: 22px;
|
||||
z-index: 999999;
|
||||
box-shadow: 0px 12px 8px -14px #c5c2c2;
|
||||
}
|
||||
|
||||
.container_box{
|
||||
width: 100%;
|
||||
margin-top: 65px;
|
||||
}
|
||||
|
||||
.config-wrapper {
|
||||
background: white;
|
||||
height: 100%;
|
||||
padding-left: 20px;
|
||||
|
||||
.alarm-time-form{
|
||||
border-top: 1px solid #E8E8E8;
|
||||
height: 80px;
|
||||
padding: 10px 0 20px;
|
||||
margin-bottom: 20px;
|
||||
.form-item{
|
||||
float: left;
|
||||
}
|
||||
b{
|
||||
float: left;
|
||||
font-size: 13px;
|
||||
margin: 0 5px;
|
||||
font-weight: 100;
|
||||
line-height: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.alarm-x-form {
|
||||
border-top: 1px solid #E8E8E8;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
Icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
&.type-form {
|
||||
padding-top: 10px;
|
||||
|
||||
.ant-form-item {
|
||||
width: 30%
|
||||
}
|
||||
.ant-form-item-label {
|
||||
padding-left: 10px;
|
||||
}
|
||||
.ant-form-item-control {
|
||||
width: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
&.action-form {
|
||||
.ant-col-3 {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.span-tag {
|
||||
border-left: 2px solid @primary-color;
|
||||
padding-left: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 40px;
|
||||
font-family: PingFangSC-Regular;
|
||||
}
|
||||
|
||||
.info-wrapper {
|
||||
border-top: 1px solid #E8E8E8;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px 10px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
|
||||
li {
|
||||
flex: 1;
|
||||
vertical-align: middle;
|
||||
margin-right: 15px;
|
||||
|
||||
.ant-select {
|
||||
margin-left: 15px;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form {
|
||||
border: 1px dashed #e29864;
|
||||
padding: 20px 0px 20px 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-list {
|
||||
line-height: 40px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
display: inline-block;
|
||||
margin: 0px 5px 10px 5px;
|
||||
|
||||
.ant-select {
|
||||
width: 150px;
|
||||
margin-right: 5px;
|
||||
|
||||
&.small-size {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
&.middle-size {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
&.large-size {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-number {
|
||||
width: 100px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.dynamic-button {
|
||||
font-size: 16px;
|
||||
transition: all 0.3s;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.dynamic-button:hover {
|
||||
&.delete {
|
||||
color: red;
|
||||
}
|
||||
|
||||
&.plus {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.dynamic-button[disabled] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dynamic-set {
|
||||
padding: 15px 10px;
|
||||
|
||||
ul{
|
||||
li{
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.form-list {
|
||||
line-height: 40px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
display: inline-block;
|
||||
margin: 0px 5px 10px 5px;
|
||||
|
||||
.ant-select {
|
||||
width: 150px;
|
||||
margin-right: 5px;
|
||||
|
||||
&.small-size {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
&.middle-size {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
&.large-size {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.strategy {
|
||||
display: inline-block;
|
||||
width: 90%;
|
||||
border: 1px dashed #dcc4af;
|
||||
padding: 15px 15px;
|
||||
margin: 0px 15px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
|
||||
.time-select {
|
||||
width: 50%;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-show{
|
||||
display: none;
|
||||
}
|
||||
171
kafka-manager-console/src/container/alarm/add-alarm/index.tsx
Normal file
171
kafka-manager-console/src/container/alarm/add-alarm/index.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import * as React from 'react';
|
||||
import './index.less';
|
||||
import { WrappedDynamicSetStrategy } from './strategy-form';
|
||||
import { Button, PageHeader, Spin, message } from 'component/antd';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { WrappedTimeForm } from './time-form';
|
||||
import { ActionForm } from './action-form';
|
||||
import { TypeForm } from './type-form';
|
||||
import { handlePageBack } from 'lib/utils';
|
||||
import { observer } from 'mobx-react';
|
||||
import { alarm } from 'store/alarm';
|
||||
import { app } from 'store/app';
|
||||
import Url from 'lib/url-parser';
|
||||
import { IStrategyExpression, IRequestParams } from 'types/alarm';
|
||||
|
||||
@observer
|
||||
export class AddAlarm extends SearchAndFilterContainer {
|
||||
public isDetailPage = window.location.pathname.includes('/alarm-detail'); // 判断是否为详情
|
||||
public strategyForm: any = null;
|
||||
public actionForm: any = null;
|
||||
public timeForm: any = null;
|
||||
public typeForm: any = null;
|
||||
|
||||
public id: number = null;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.id = Number(url.search.id);
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
alarm.getMonitorType();
|
||||
alarm.setLoading(true);
|
||||
app.getAppList();
|
||||
if (this.id || this.id === 0) {
|
||||
await alarm.getMonitorDetail(this.id);
|
||||
this.initMonitorDetailData();
|
||||
}
|
||||
alarm.setLoading(false);
|
||||
}
|
||||
|
||||
public initMonitorDetailData() {
|
||||
if (alarm.monitorStrategyDetail.monitorRule) {
|
||||
if (!this.strategyForm || !this.actionForm || !this.typeForm || !this.timeForm) {
|
||||
return;
|
||||
}
|
||||
const monitorRule = alarm.monitorStrategyDetail.monitorRule || {} as IRequestParams;
|
||||
|
||||
this.timeForm.updateFormData(monitorRule);
|
||||
this.typeForm.updateFormData(monitorRule);
|
||||
this.actionForm.updateFormData(monitorRule);
|
||||
this.strategyForm.updateFormValue(monitorRule);
|
||||
}
|
||||
}
|
||||
|
||||
public handleSubmit = () => {
|
||||
if (!this.strategyForm || !this.actionForm || !this.typeForm || !this.timeForm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = this.generateRequestParams() as IRequestParams;
|
||||
if (!params) return;
|
||||
|
||||
(this.id || this.id === 0) ?
|
||||
alarm.modifyMonitorStrategy({ id: this.id, ...params }) : alarm.addMonitorStategy(params);
|
||||
}
|
||||
|
||||
public handleResetForm = (id?: number) => {
|
||||
if (id || id === 0) {
|
||||
alarm.getMonitorDetail(this.id);
|
||||
this.initMonitorDetailData();
|
||||
} else {
|
||||
if (!this.strategyForm || !this.actionForm || !this.typeForm || !this.timeForm) {
|
||||
return;
|
||||
}
|
||||
this.typeForm.resetFormData();
|
||||
this.timeForm.resetFormData();
|
||||
this.actionForm.resetFormData();
|
||||
|
||||
this.strategyForm.resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
public generateRequestParams() {
|
||||
const actionValue = this.actionForm.getFormData();
|
||||
const timeValue = this.timeForm.getFormData();
|
||||
const typeValue = this.typeForm.getFormData().typeValue;
|
||||
let strategyList = this.strategyForm.getFormValidateData();
|
||||
const filterObj = this.typeForm.getFormData().filterObj;
|
||||
// tslint:disable-next-line:max-line-length
|
||||
if (!actionValue || !timeValue || !typeValue || !strategyList.length || !filterObj || !filterObj.filterList.length) {
|
||||
message.error('请正确填写必填项');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filterObj.monitorType === 'online-kafka-topic-throttled') {
|
||||
filterObj.filterList.push({
|
||||
tkey: 'app',
|
||||
topt: '=',
|
||||
tval: [typeValue.app],
|
||||
});
|
||||
}
|
||||
strategyList = strategyList.map((row: IStrategyExpression) => {
|
||||
return {
|
||||
...row,
|
||||
metric: filterObj.monitorType,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
appId: typeValue.app,
|
||||
name: typeValue.alarmName,
|
||||
periodDaysOfWeek: timeValue.weeks.join(','),
|
||||
periodHoursOfDay: timeValue.hours.join(','),
|
||||
priority: actionValue.level,
|
||||
strategyActionList: [{
|
||||
callback: actionValue.callback,
|
||||
notifyGroup: actionValue.acceptGroup,
|
||||
converge: actionValue.alarmPeriod + ',' + actionValue.alarmTimes,
|
||||
type: 'notify',
|
||||
sendRecovery: 1,
|
||||
}],
|
||||
strategyExpressionList: strategyList,
|
||||
strategyFilterList: filterObj.filterList,
|
||||
} as IRequestParams;
|
||||
}
|
||||
|
||||
public renderAlarmStrategy() {
|
||||
return (
|
||||
<div className="config-wrapper">
|
||||
<span className="span-tag">报警策略</span>
|
||||
<div className="info-wrapper">
|
||||
<WrappedDynamicSetStrategy wrappedComponentRef={(form: any) => this.strategyForm = form} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderTimeForm() {
|
||||
return (
|
||||
<>
|
||||
<WrappedTimeForm wrappedComponentRef={(form: any) => this.timeForm = form} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Spin spinning={alarm.loading}>
|
||||
<div className={this.isDetailPage ? '' : 'container_box'}>
|
||||
<PageHeader
|
||||
className={this.isDetailPage ? 'is-show' : 'btn-group'}
|
||||
onBack={() => handlePageBack('/alarm')}
|
||||
title={(this.id || this.id === 0) ? '修改告警配置' : '新建告警配置'}
|
||||
extra={[
|
||||
<Button key="1" type="primary" onClick={() => this.handleSubmit()}>提交</Button>,
|
||||
<Button key="2" onClick={() => this.handleResetForm(this.id)}>重置</Button>,
|
||||
]}
|
||||
/>
|
||||
<TypeForm
|
||||
ref={(form) => this.typeForm = form}
|
||||
/>
|
||||
{this.renderAlarmStrategy()}
|
||||
{this.renderTimeForm()}
|
||||
<ActionForm ref={(actionForm) => this.actionForm = actionForm} />
|
||||
</div>
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
import * as React from 'react';
|
||||
import { Icon, InputNumber, Select, message, Form, Tooltip } from 'component/antd';
|
||||
import { equalList, funcKeyMap, funcList } from 'constants/strategy';
|
||||
import { IStringMap } from 'types/base-type';
|
||||
import { IRequestParams } from 'types/alarm';
|
||||
import { IFormSelect, IFormItem, FormItemType } from 'component/x-form';
|
||||
import { searchProps } from 'constants/table';
|
||||
|
||||
interface IDynamicProps {
|
||||
form: any;
|
||||
formData?: any;
|
||||
maxLimit?: number;
|
||||
}
|
||||
interface ICRUDItem {
|
||||
id: string;
|
||||
func: string;
|
||||
eopt?: string;
|
||||
threshold?: number;
|
||||
period?: number;
|
||||
count?: number;
|
||||
day?: number;
|
||||
}
|
||||
const commonKeys = ['eopt', 'threshold', 'func'];
|
||||
|
||||
class DynamicSetStrategy extends React.Component<IDynamicProps> {
|
||||
public isDetailPage = window.location.pathname.includes('/alarm-detail'); // 判断是否为详情
|
||||
public crudList = [] as ICRUDItem[];
|
||||
public state = {
|
||||
shouldUpdate: false,
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
if (!this.crudList.length) {
|
||||
const id = `0_`;
|
||||
this.crudList.push({
|
||||
id,
|
||||
func: 'happen',
|
||||
});
|
||||
}
|
||||
this.updateRender();
|
||||
}
|
||||
|
||||
public updateRender() {
|
||||
this.setState({
|
||||
shouldUpdate: !this.state.shouldUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
public resetForm() {
|
||||
const { resetFields } = this.props.form;
|
||||
resetFields();
|
||||
}
|
||||
|
||||
public dealFormParams(monitorRule: IRequestParams) {
|
||||
const initialCrudList = [] as ICRUDItem[];
|
||||
|
||||
if (monitorRule.strategyExpressionList) {
|
||||
const expressionList = monitorRule.strategyExpressionList;
|
||||
expressionList.map((row: any, index) => {
|
||||
const obj = {} as any;
|
||||
for (const key of commonKeys) {
|
||||
obj[key] = row[key];
|
||||
}
|
||||
const otherKeys = funcKeyMap[row.func] as string[];
|
||||
// 除去commonKeys中的key 其他值在提交时全塞到params中 故在编辑详情渲染时再拆回来
|
||||
const parmas = row.params ? row.params.split(',').map((row: string) => +row) : [];
|
||||
otherKeys.forEach((line: string, i: number) => {
|
||||
obj[line] = parmas[i] || 0;
|
||||
});
|
||||
obj.id = `${index}_`;
|
||||
|
||||
initialCrudList.push(obj);
|
||||
});
|
||||
}
|
||||
return initialCrudList;
|
||||
}
|
||||
|
||||
public updateFormValue(monitorRule: IRequestParams) {
|
||||
const { setFieldsValue } = this.props.form;
|
||||
const initialCrudList = this.dealFormParams(monitorRule);
|
||||
if (!initialCrudList.length) return;
|
||||
|
||||
const filledKeys = ['period'].concat(commonKeys);
|
||||
const formKeyMap = {
|
||||
happen: ['count'].concat(filledKeys),
|
||||
ndiff: ['count'].concat(filledKeys),
|
||||
all: [].concat(filledKeys),
|
||||
pdiff: [].concat(filledKeys),
|
||||
sum: [].concat(filledKeys),
|
||||
c_avg_rate_abs: ['day'].concat(filledKeys),
|
||||
} as {
|
||||
[key: string]: string[],
|
||||
};
|
||||
const feildValue = {
|
||||
|
||||
} as any;
|
||||
|
||||
for (const item of initialCrudList) {
|
||||
for (const key of formKeyMap[item.func]) {
|
||||
feildValue[item.id + '-' + key] = (item as any)[key];
|
||||
}
|
||||
}
|
||||
setFieldsValue(feildValue);
|
||||
this.crudList = initialCrudList;
|
||||
this.updateRender();
|
||||
}
|
||||
|
||||
public getFormValidateData() {
|
||||
let value = [] as IStringMap[];
|
||||
const { crudList } = this;
|
||||
|
||||
this.props.form.validateFields((err: Error, values: any) => {
|
||||
if (!err) {
|
||||
let strategyList = [];
|
||||
for (const item of crudList) {
|
||||
const lineValue = {} as IStringMap;
|
||||
const paramsArray = [] as number[];
|
||||
|
||||
// 不在commonKeys里的塞到params
|
||||
for (const key of Object.keys(values)) {
|
||||
if (key.indexOf(item.id) > -1) {
|
||||
const finalKey = key.substring(key.indexOf('-') + 1);
|
||||
if (commonKeys.indexOf(finalKey) < 0) { // 不在commonKeys里的塞到params 奇奇怪怪的接口
|
||||
paramsArray.push(finalKey === 'day' ? values[key] * 24 * 60 * 60 : values[key]); // 按接口单位天的时候需要换算成秒
|
||||
} else { // 在commonKeys里直接赋值
|
||||
lineValue[finalKey] = values[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lineValue.func === 'happen' && paramsArray.length > 1 && paramsArray[0] < paramsArray[1]) {
|
||||
strategyList = []; // 清空赋值
|
||||
return message.error('周期值应大于次数') ;
|
||||
}
|
||||
|
||||
lineValue.params = paramsArray.join(',');
|
||||
strategyList.push(lineValue);
|
||||
}
|
||||
value = strategyList;
|
||||
}
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public remove = (curr: string) => {
|
||||
const { crudList } = this;
|
||||
if (crudList.length <= 1) {
|
||||
return message.info('至少保留一项');
|
||||
}
|
||||
|
||||
const index = crudList.findIndex(item => item.id === curr);
|
||||
|
||||
crudList.splice(index, 1);
|
||||
this.updateRender();
|
||||
}
|
||||
|
||||
public add = () => {
|
||||
const { maxLimit = 5 } = this.props;
|
||||
const { crudList } = this;
|
||||
if (crudList.length >= maxLimit) {
|
||||
return message.info('已达最大数量');
|
||||
}
|
||||
|
||||
const id = `${crudList.length}_`;
|
||||
crudList.push({
|
||||
id,
|
||||
func: 'happen',
|
||||
});
|
||||
this.updateRender();
|
||||
}
|
||||
|
||||
public onFuncTypeChange = (e: string, key: string) => {
|
||||
const { crudList } = this;
|
||||
const index = crudList.findIndex(row => row.id === key);
|
||||
|
||||
if (index > -1) {
|
||||
crudList[index].func = e;
|
||||
}
|
||||
this.updateRender();
|
||||
}
|
||||
|
||||
public getFormItem(item: IFormItem) {
|
||||
switch (item.type) {
|
||||
default:
|
||||
case FormItemType.input:
|
||||
return <InputNumber min={0} key={item.key} {...item.attrs} disabled={this.isDetailPage} />;
|
||||
case FormItemType.select:
|
||||
return (
|
||||
<Select
|
||||
key={item.key}
|
||||
{...item.attrs}
|
||||
disabled={this.isDetailPage}
|
||||
{...searchProps}
|
||||
>
|
||||
{(item as IFormSelect).options && (item as IFormSelect).options.map((v, index) => (
|
||||
<Select.Option
|
||||
key={v.value || v.key || index}
|
||||
value={v.value}
|
||||
>
|
||||
{v.label.length > 15 ? <Tooltip placement="bottomLeft" title={v.label}>
|
||||
{v.label}
|
||||
</Tooltip> : v.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public getFuncItem(row: ICRUDItem) {
|
||||
const key = row.id;
|
||||
const funcType = row.func;
|
||||
let element = null;
|
||||
const common = (
|
||||
<>
|
||||
在最近
|
||||
{this.renderFormItem({ type: 'input', key: key + '-period', defaultValue: row.period } as IFormItem)}
|
||||
个周期内
|
||||
</>
|
||||
);
|
||||
const equalItem = {
|
||||
type: 'select',
|
||||
attrs: { className: 'small-size' },
|
||||
defaultValue: row.eopt || '=',
|
||||
options: equalList,
|
||||
key: key + '-eopt',
|
||||
} as IFormSelect;
|
||||
|
||||
switch (funcType) {
|
||||
case 'happen':
|
||||
case 'ndiff':
|
||||
element = (
|
||||
<>
|
||||
{common}
|
||||
{this.renderFormItem({ type: 'input', key: key + '-count', defaultValue: row.count } as IFormItem)}
|
||||
次
|
||||
{this.renderFormItem(equalItem)}
|
||||
{this.renderFormItem({ type: 'input', key: key + '-threshold', defaultValue: row.threshold } as IFormItem)}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case 'all':
|
||||
case 'diff':
|
||||
case 'max':
|
||||
case 'min':
|
||||
case 'sum':
|
||||
case 'avg':
|
||||
element = (
|
||||
<>
|
||||
{common}
|
||||
{this.renderFormItem(equalItem)}
|
||||
{this.renderFormItem({ type: 'input', key: key + '-threshold', defaultValue: row.threshold } as IFormItem)}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case 'c_avg_rate_abs':
|
||||
case 'c_avg_rate':
|
||||
element = (
|
||||
<>
|
||||
{common},平均值相对
|
||||
{this.renderFormItem({ type: 'input', key: key + '-day', defaultValue: row.day } as IFormItem)}
|
||||
天
|
||||
{this.renderFormItem(equalItem)}
|
||||
{this.renderFormItem({ type: 'input', key: key + '-threshold', defaultValue: row.threshold } as IFormItem)}
|
||||
%
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case 'c_avg_abs':
|
||||
case 'c_avg':
|
||||
element = (
|
||||
<>
|
||||
{common},平均值相对
|
||||
{this.renderFormItem({ type: 'input', key: key + '-day', defaultValue: row.day } as IFormItem)}
|
||||
天
|
||||
{this.renderFormItem(equalItem)}
|
||||
{this.renderFormItem({ type: 'input', key: key + '-threshold', defaultValue: row.threshold } as IFormItem)}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case 'pdiff':
|
||||
element = (
|
||||
<>
|
||||
{common}
|
||||
{this.renderFormItem(equalItem)}
|
||||
{this.renderFormItem({ type: 'input', key: key + '-threshold', defaultValue: row.threshold } as IFormItem)}
|
||||
%
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
public renderFormList(row: ICRUDItem) {
|
||||
const key = row.id;
|
||||
const funcType = row.func;
|
||||
|
||||
return (
|
||||
<div key={key} className="form-list">
|
||||
{this.renderFormItem({
|
||||
type: 'select',
|
||||
defaultValue: funcType,
|
||||
attrs: {
|
||||
onChange: (e: string) => this.onFuncTypeChange(e, key),
|
||||
},
|
||||
options: funcList,
|
||||
key: key + '-func',
|
||||
} as IFormSelect)}
|
||||
{this.getFuncItem(row)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderFormItem(item: IFormItem) {
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
const initialValue = item.defaultValue || '';
|
||||
const getFieldValue = {
|
||||
initialValue,
|
||||
rules: item.rules || [{ required: true, message: '请填写' }],
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
key={item.key}
|
||||
>
|
||||
{getFieldDecorator(item.key, getFieldValue)(
|
||||
this.getFormItem(item),
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { crudList } = this;
|
||||
const { maxLimit = 5 } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{crudList.map((row, index) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
{this.renderFormList(row)}
|
||||
{
|
||||
crudList.length > 1 ? (
|
||||
<Icon
|
||||
className={this.isDetailPage ? 'is-show' : 'dynamic-button delete'}
|
||||
type="minus-circle-o"
|
||||
onClick={() => this.remove(row.id)}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
{index === crudList.length - 1 && crudList.length < maxLimit ? (
|
||||
<Icon
|
||||
className={this.isDetailPage ? 'is-show' : 'dynamic-button plus'}
|
||||
type="plus-circle-o"
|
||||
onClick={() => this.add()}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const WrappedDynamicSetStrategy = Form.create({ name: 'dynamic_form_item' })(DynamicSetStrategy);
|
||||
@@ -0,0 +1,137 @@
|
||||
import { getAlarmTime, getAlarmWeek } from './config';
|
||||
import * as React from 'react';
|
||||
import { IRequestParams, IAlarmTime } from 'types/alarm';
|
||||
import { Checkbox, TimePicker, Form } from 'component/antd';
|
||||
import { weekOptions } from 'constants/status-map';
|
||||
|
||||
import moment = require('moment');
|
||||
|
||||
interface ITimeProps {
|
||||
form?: any;
|
||||
formData?: any;
|
||||
}
|
||||
|
||||
export class TimeForm extends React.Component<ITimeProps> {
|
||||
public isDetailPage = window.location.pathname.includes('/alarm-detail'); // 判断是否为详情
|
||||
public $form: any = null;
|
||||
public weeks: number[] = [0, 1, 2, 3, 4, 5, 6, 7];
|
||||
public startTime: number = 0;
|
||||
public endTime: number = 23;
|
||||
|
||||
public getFormData() {
|
||||
let value = null as IAlarmTime;
|
||||
this.props.form.validateFields((error: Error, result: any) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
const start = Number(moment(result.startTime).format('HH'));
|
||||
const end = Number(moment(result.endTime).format('HH'));
|
||||
const timeArr = getAlarmTime().defaultTime;
|
||||
const hours = timeArr.slice(start, end + 1);
|
||||
value = {
|
||||
weeks: result.weeks,
|
||||
hours,
|
||||
};
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
public resetFormData() {
|
||||
const { defaultTime } = getAlarmTime();
|
||||
const { defWeek } = getAlarmWeek();
|
||||
this.props.form.setFieldsValue({
|
||||
hours: defaultTime,
|
||||
weeks: defWeek,
|
||||
startTime: moment(0, 'HH'),
|
||||
endTime: moment(23, 'HH'),
|
||||
});
|
||||
}
|
||||
|
||||
public updateFormData = (monitorRule: IRequestParams) => {
|
||||
const selectHours = monitorRule.periodHoursOfDay.split(',').map(item => +item);
|
||||
const selectWeek = monitorRule.periodDaysOfWeek.split(',').map(item => +item);
|
||||
|
||||
this.props.form.setFieldsValue({
|
||||
// hours: selectHours,
|
||||
weeks: selectWeek,
|
||||
startTime: moment(selectHours[0], 'HH'),
|
||||
endTime: moment(selectHours[selectHours.length - 1], 'HH'),
|
||||
});
|
||||
this.startTime = selectHours[0];
|
||||
this.endTime = selectHours[selectHours.length - 1];
|
||||
}
|
||||
public onStartChange = (time: any, timeString: string) => {
|
||||
this.startTime = Number(timeString);
|
||||
}
|
||||
public disabledHours = () => {
|
||||
const hours = [] as number[];
|
||||
for (let i = 0; i < this.startTime; i++) {
|
||||
hours.push(i);
|
||||
}
|
||||
return hours;
|
||||
}
|
||||
|
||||
public render() {
|
||||
// const formData = {};
|
||||
// {/* <div className="alarm-x-form">
|
||||
// <XFormComponent
|
||||
// ref={form => this.$form = form}
|
||||
// formData={formData}
|
||||
// formMap={xTimeFormMap}
|
||||
// formLayout={formLayout}
|
||||
// />
|
||||
// </div> */}
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
const format = 'HH';
|
||||
return (
|
||||
<div className="config-wrapper">
|
||||
<span className="span-tag">生效时间</span>
|
||||
<div className="alarm-time-form">
|
||||
<Form name="basic" >
|
||||
<b>在每</b>
|
||||
<Form.Item label="" key={1} className="form-item">
|
||||
{getFieldDecorator('weeks', {
|
||||
initialValue: this.weeks,
|
||||
rules: [{ required: true, message: '请选择周期' }],
|
||||
})(
|
||||
<Checkbox.Group
|
||||
options={weekOptions}
|
||||
disabled={this.isDetailPage}
|
||||
/>)}
|
||||
</Form.Item>
|
||||
<b>的</b>
|
||||
<Form.Item label="" key={2} className="form-item">
|
||||
{getFieldDecorator('startTime', {
|
||||
initialValue: moment(this.startTime, format),
|
||||
rules: [{ required: true, message: '请选择开始时间' }],
|
||||
})(
|
||||
<TimePicker
|
||||
key={1}
|
||||
format={format}
|
||||
style={{width: 60}}
|
||||
onChange={this.onStartChange}
|
||||
disabled={this.isDetailPage}
|
||||
/>)}
|
||||
</Form.Item>
|
||||
<b>~</b>
|
||||
<Form.Item label="" key={3} className="form-item">
|
||||
{getFieldDecorator('endTime', {
|
||||
initialValue: moment(this.endTime, format),
|
||||
rules: [{ required: true, message: '请选择结束时间' }],
|
||||
})(
|
||||
<TimePicker
|
||||
key={2}
|
||||
format={format}
|
||||
disabledHours={this.disabledHours}
|
||||
style={{width: 60}}
|
||||
disabled={this.isDetailPage}
|
||||
/>)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const WrappedTimeForm = Form.create({ name: 'dynamic_time_form' })(TimeForm);
|
||||
@@ -0,0 +1,74 @@
|
||||
import { XFormComponent } from 'component/x-form';
|
||||
import { xTypeFormMap } from './config';
|
||||
import * as React from 'react';
|
||||
import { IRequestParams, ITypeForm } from 'types/alarm';
|
||||
import { app } from 'store/app';
|
||||
import { observer } from 'mobx-react';
|
||||
import { WrappedDynamicSetFilter } from './filter-form';
|
||||
|
||||
@observer
|
||||
export class TypeForm extends React.Component {
|
||||
|
||||
public $form: any = null;
|
||||
public filterForm: any = null;
|
||||
|
||||
public getFormData() {
|
||||
const filterObj = this.filterForm.getFormValidateData();
|
||||
let typeValue = null as ITypeForm;
|
||||
this.$form.validateFields((error: Error, result: ITypeForm) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
typeValue = result;
|
||||
});
|
||||
const valueObj = {
|
||||
typeValue,
|
||||
filterObj,
|
||||
};
|
||||
return valueObj;
|
||||
}
|
||||
|
||||
public resetFormData() {
|
||||
this.$form.resetFields();
|
||||
this.filterForm.resetForm();
|
||||
}
|
||||
|
||||
public updateFormData(monitorRule: IRequestParams) {
|
||||
this.$form.setFieldsValue({
|
||||
app: monitorRule.appId,
|
||||
alarmName: monitorRule.name,
|
||||
});
|
||||
this.filterForm.initFormValue(monitorRule);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const formData = {};
|
||||
xTypeFormMap[1].options = app.data.map(item => ({
|
||||
label: item.name,
|
||||
value: item.appId,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="config-wrapper">
|
||||
<span className="span-tag">基本信息</span>
|
||||
<div className="alarm-x-form type-form">
|
||||
<XFormComponent
|
||||
ref={form => this.$form = form}
|
||||
formData={formData}
|
||||
formMap={xTypeFormMap}
|
||||
layout="inline"
|
||||
/>
|
||||
</div>
|
||||
</div >
|
||||
<div className="config-wrapper">
|
||||
<span className="span-tag">选择指标</span>
|
||||
<div className="alarm-x-form type-form">
|
||||
<WrappedDynamicSetFilter wrappedComponentRef={(form: any) => this.filterForm = form} />
|
||||
</div>
|
||||
</div >
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import * as React from 'react';
|
||||
import { Table, Button } from 'component/antd';
|
||||
import { urlPrefix } from 'constants/left-menu';
|
||||
import moment from 'moment';
|
||||
import { alarm } from 'store/alarm';
|
||||
import Url from 'lib/url-parser';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { IMonitorAlerts } from 'types/base-type';
|
||||
import './index.less';
|
||||
import { observer } from 'mobx-react';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class AlarmHistory extends SearchAndFilterContainer {
|
||||
public id: number = null;
|
||||
public startTime: any = moment().subtract(3, 'day').format('x');
|
||||
public endTime: any = moment().endOf('day').format('x');
|
||||
|
||||
public state = {
|
||||
filterStatus: false,
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.id = Number(url.search.id);
|
||||
}
|
||||
|
||||
public historyCreateTime(value?: number) {
|
||||
this.startTime = value ? moment().subtract(7, 'day').format('x') : moment().subtract(3, 'day').format('x');
|
||||
this.endTime = moment().format('x');
|
||||
alarm.getMonitorAlerts(this.id, this.startTime, this.endTime);
|
||||
}
|
||||
|
||||
public historySelect() {
|
||||
return(
|
||||
<>
|
||||
<div className="alarm-history-day">
|
||||
<Button onClick={() => this.historyCreateTime()}>近三天</Button>
|
||||
<Button onClick={() => this.historyCreateTime(7)}>近一周</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public historyTable() {
|
||||
const monitorAlerts: IMonitorAlerts[] = alarm.monitorAlerts ? alarm.monitorAlerts : [];
|
||||
const alertStatus = Object.assign({
|
||||
title: '状态',
|
||||
dataIndex: 'alertStatus',
|
||||
key: 'alertStatus',
|
||||
filters: [{ text: '故障', value: '0' }, { text: '已恢复', value: '1' }],
|
||||
onFilter: (value: string, record: IMonitorAlerts) => record.alertStatus === Number(value),
|
||||
render: (t: number) => t === 0 ? '故障' : '已恢复',
|
||||
}, this.renderColumnsFilter('filterStatus'));
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '监控名称',
|
||||
dataIndex: 'monitorName',
|
||||
key: 'monitorName',
|
||||
render: (text: string, record: IMonitorAlerts) => (
|
||||
<a href={`${urlPrefix}/alarm/history-detail?alertId=${record.alertId}`}> {text} </a>),
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
render: (time: number) => moment(time).format(timeFormat),
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
render: (time: number) => moment(time).format(timeFormat),
|
||||
},
|
||||
alertStatus,
|
||||
{
|
||||
title: '监控级别',
|
||||
dataIndex: 'monitorPriority',
|
||||
key: 'monitorPriority',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Table rowKey="key" dataSource={monitorAlerts} columns={columns} loading={alarm.loading}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
alarm.getMonitorAlerts(this.id, this.startTime, this.endTime);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return(
|
||||
<>
|
||||
{this.historySelect()}
|
||||
{this.historyTable()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import * as React from 'react';
|
||||
import { createMonitorSilences } from 'container/modal';
|
||||
import { IMonitorAlert, IMonitorMetric } from 'types/base-type';
|
||||
import { Divider, Table, Button, PageHeader, Spin, Tooltip } from 'component/antd';
|
||||
import { alarm } from 'store/alarm';
|
||||
import { observer } from 'mobx-react';
|
||||
import { handlePageBack } from 'lib/utils';
|
||||
import LineChart, { hasData } from 'component/chart/line-chart';
|
||||
import { EChartOption } from 'echarts';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
import Url from 'lib/url-parser';
|
||||
import moment = require('moment');
|
||||
import './index.less';
|
||||
|
||||
@observer
|
||||
export class HistoryDetail extends React.Component {
|
||||
public alertId: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.alertId = Number(url.search.alertId);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
alarm.getAlertsDetail(this.alertId);
|
||||
}
|
||||
|
||||
public getChartOption = () => {
|
||||
return alarm.getMetircHistoryChartOptions();
|
||||
}
|
||||
|
||||
public renderNoData = (height?: number) => {
|
||||
const style = { height: `${height}px`, lineHeight: `${height}px` };
|
||||
return <div className="no-data-info" style={{ ...style }} key="noData">暂无数据</div>;
|
||||
}
|
||||
|
||||
public renderLoading = (height?: number) => {
|
||||
const style = { height: `${height}px`, lineHeight: `${height}px` };
|
||||
return <div className="no-data-info" style={{ ...style }} key="loading"><Spin /></div>;
|
||||
}
|
||||
|
||||
public renderEchart = (options: EChartOption, loading = false) => {
|
||||
const data = hasData(options);
|
||||
if (loading) return this.renderLoading(400);
|
||||
if (!data) return this.renderNoData(400);
|
||||
return (
|
||||
<div className="chart">
|
||||
<LineChart height={400} options={options} key="chart" />
|
||||
</div>);
|
||||
}
|
||||
|
||||
public renderHistoricalTraffic(metric: IMonitorMetric) {
|
||||
const option = this.getChartOption() as EChartOption;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="history-left">
|
||||
<div className="chart-box-0">
|
||||
<div className="chart-title metric-head">
|
||||
<span>{metric.metric}</span>
|
||||
</div>
|
||||
<Divider />
|
||||
{this.renderEchart(option)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public renderAlarmEventDetails(alert: IMonitorAlert) {
|
||||
const pointsColumns = [
|
||||
{
|
||||
title: 'timestamp',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
render: (t: number) => moment(t * 1000).format(timeFormat),
|
||||
},
|
||||
{
|
||||
title: 'value',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
}];
|
||||
return (
|
||||
<>
|
||||
<div className="history-right">
|
||||
<div className="history-right-header">
|
||||
<h2>报警事件详情</h2>
|
||||
<Button onClick={() => { createMonitorSilences(alert.monitorId, alert.monitorName); }}>快速屏蔽</Button>
|
||||
</div>
|
||||
<Divider className="history-right-divider" />
|
||||
<ul>
|
||||
<li><b>监控名称:</b>{alert.monitorName}</li>
|
||||
<li><b>告警状态:</b>{alert.alertStatus === 0 ? '故障' : '已恢复'}</li>
|
||||
<li><b>告警组:</b>{alert.groups ? alert.groups.join('、') : null}</li>
|
||||
<li><b>告警指标:</b>{alert.metric}</li>
|
||||
<li><b>告警开始时间:</b>{moment(alert.startTime).format(timeFormat)}</li>
|
||||
<li><b>告警结束时间:</b>{moment(alert.endTime).format(timeFormat)}</li>
|
||||
<li><b>监控级别:</b>{alert.monitorPriority}级告警</li>
|
||||
<li><b>触发值:</b>{alert.value}</li>
|
||||
<li>
|
||||
<b>表达式:</b>
|
||||
<Tooltip placement="bottomLeft" title={alert.info} >
|
||||
{alert.info}
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
<h4>现场值:</h4>
|
||||
<Table
|
||||
rowKey="timestamp"
|
||||
dataSource={alert.points}
|
||||
columns={pointsColumns}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
bordered={true}
|
||||
scroll={{ y: 260 }}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
{alarm.alertsDetail &&
|
||||
<>
|
||||
<PageHeader
|
||||
className="detail topic-detail-header"
|
||||
onBack={() => handlePageBack('/alarm')}
|
||||
title={`${alarm.monitorAlert.monitorName || ''}`}
|
||||
/>
|
||||
<div className="alarm-history">
|
||||
{this.renderHistoricalTraffic(alarm.monitorMetric)}
|
||||
{this.renderAlarmEventDetails(alarm.monitorAlert)}
|
||||
</div>
|
||||
</>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
.alarm-history{
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.history-left{
|
||||
width: 60%;
|
||||
}
|
||||
.history-right{
|
||||
width: 30%;
|
||||
padding: 15px 20px;
|
||||
background: #fff;
|
||||
.history-right-divider{
|
||||
margin-top: -5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.history-right-header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
h2{
|
||||
font-size: 14px;
|
||||
line-height: 38px;
|
||||
}
|
||||
}
|
||||
ul{
|
||||
li{
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis; //超出部分以省略号显示
|
||||
white-space: nowrap;
|
||||
b{
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alarm-history-day{
|
||||
width: 160px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.monitor-detail{
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.metric-head{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
span{
|
||||
font-size: 13px;
|
||||
}
|
||||
a{
|
||||
margin: 0 5px;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import * as React from 'react';
|
||||
import { Tabs, PageHeader, Button } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import { AlarmHistory } from './alarm-history';
|
||||
import { handleTabKey } from 'lib/utils';
|
||||
import { ShieldHistory } from './shield-history';
|
||||
import { IXFormWrapper } from 'types/base-type';
|
||||
import { alarm } from 'store/alarm';
|
||||
import { IMonitorStrategyDetail } from 'types/alarm';
|
||||
import { urlPrefix } from 'constants/left-menu';
|
||||
import { createMonitorSilences } from 'container/modal';
|
||||
import { AddAlarm } from '../add-alarm';
|
||||
import { handlePageBack } from 'lib/utils';
|
||||
import Url from 'lib/url-parser';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
@observer
|
||||
export class AlarmDetail extends React.Component {
|
||||
public id: number = null;
|
||||
public monitorName: any = null;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.id = Number(url.search.id);
|
||||
}
|
||||
|
||||
public render() {
|
||||
let baseInfo = {} as IMonitorStrategyDetail;
|
||||
if (alarm.monitorStrategyDetail) {
|
||||
baseInfo = alarm.monitorStrategyDetail;
|
||||
}
|
||||
this.monitorName = baseInfo.name;
|
||||
return(
|
||||
<>
|
||||
<PageHeader
|
||||
className="detail topic-detail-header"
|
||||
onBack={() => handlePageBack('/alarm')}
|
||||
title={`${baseInfo.name || ''}`}
|
||||
extra={[
|
||||
<Button key="1" type="primary">
|
||||
<a href={`${urlPrefix}/alarm/modify?id=${this.id}`}>编辑</a>
|
||||
</Button>,
|
||||
<Button onClick={() => {createMonitorSilences(this.id, this.monitorName); }} key="2" >
|
||||
屏蔽
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
<Tabs activeKey={location.hash.substr(1) || '1'} type="card" onChange={handleTabKey}>
|
||||
<TabPane tab="基本信息" key="1">
|
||||
<AddAlarm />
|
||||
</TabPane>
|
||||
<TabPane tab="告警历史" key="2">
|
||||
<AlarmHistory />
|
||||
</TabPane>
|
||||
<TabPane tab="屏蔽历史" key="3">
|
||||
<ShieldHistory />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import * as React from 'react';
|
||||
import { Table, notification, Modal, Popconfirm } from 'component/antd';
|
||||
import moment from 'moment';
|
||||
import { alarm } from 'store/alarm';
|
||||
import { wrapper } from 'store';
|
||||
import Url from 'lib/url-parser';
|
||||
import { IMonitorSilences, IXFormWrapper } from 'types/base-type';
|
||||
import { observer } from 'mobx-react';
|
||||
import './index.less';
|
||||
import { timeFormat } from 'constants/strategy';
|
||||
|
||||
@observer
|
||||
export class ShieldHistory extends React.Component {
|
||||
public id: number = null;
|
||||
|
||||
private xFormWrapper: IXFormWrapper;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const url = Url();
|
||||
this.id = Number(url.search.id);
|
||||
}
|
||||
|
||||
public silencesDetail(record: IMonitorSilences) {
|
||||
alarm.getSilencesDetail(record.silenceId).then((data) => {
|
||||
if (alarm.silencesDetail) {
|
||||
this.modifyInfo(alarm.silencesDetail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public modifyInfo(record: IMonitorSilences) {
|
||||
Modal.info({
|
||||
title: '详情',
|
||||
content: (
|
||||
<ul className="monitor-detail">
|
||||
<li><b>告警名称:</b>{record.monitorName}</li>
|
||||
<li><b>开始时间:</b>{moment(record.startTime).format(timeFormat)}</li>
|
||||
<li><b>结束时间:</b>{moment(record.endTime).format(timeFormat)}</li>
|
||||
<li><b>说明:</b>{record.description}</li>
|
||||
</ul>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
public modifyMonitor(record: IMonitorSilences) {
|
||||
this.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,
|
||||
disabled: false,
|
||||
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,
|
||||
}],
|
||||
attrs: {
|
||||
disabled: false,
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
],
|
||||
formData: {
|
||||
monitorName: record.monitorName,
|
||||
beginEndTime: [moment(record.startTime), moment(record.endTime)],
|
||||
description: record.description,
|
||||
},
|
||||
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'),
|
||||
id: record.silenceId,
|
||||
monitorId: record.monitorId,
|
||||
} as IMonitorSilences;
|
||||
alarm.modifyMask(params, this.id).then(data => {
|
||||
notification.success({ message: '修改成功' });
|
||||
});
|
||||
},
|
||||
};
|
||||
wrapper.open(this.xFormWrapper);
|
||||
}
|
||||
|
||||
public deleteSilences(record: IMonitorSilences) {
|
||||
alarm.deleteSilences(this.id, record.silenceId).then(data => {
|
||||
notification.success({ message: '删除成功' });
|
||||
});
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
alarm.getMonitorSilences(this.id);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const monitorSilences: IMonitorSilences[] = alarm.monitorSilences ? alarm.monitorSilences : [];
|
||||
const monitorColumns = [
|
||||
{
|
||||
title: '监控名称',
|
||||
dataIndex: 'monitorName',
|
||||
key: 'monitorName',
|
||||
render: (text: string) => <span>{text}</span>,
|
||||
}, {
|
||||
title: '开始时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
render: (t: number) => moment(t).format(timeFormat),
|
||||
}, {
|
||||
title: '结束时间',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
render: (t: number) => moment(t).format(timeFormat),
|
||||
}, {
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'option',
|
||||
key: 'option',
|
||||
render: (action: any, record: IMonitorSilences) => {
|
||||
return(
|
||||
<>
|
||||
<a onClick={() => this.modifyMonitor(record)} className="action-button">修改</a>
|
||||
<a onClick={() => this.silencesDetail(record)} className="action-button">详情</a>
|
||||
<Popconfirm
|
||||
title="确定删除?"
|
||||
onConfirm={() => this.deleteSilences(record)}
|
||||
>
|
||||
<a>删除</a>
|
||||
</Popconfirm>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return(
|
||||
<>
|
||||
<Table dataSource={monitorSilences} columns={monitorColumns} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
87
kafka-manager-console/src/container/alarm/alarm-list.tsx
Normal file
87
kafka-manager-console/src/container/alarm/alarm-list.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as React from 'react';
|
||||
import { Table, Button } from 'component/antd';
|
||||
import { SearchAndFilterContainer } from 'container/search-filter';
|
||||
import { observer } from 'mobx-react';
|
||||
import { app } from 'store/app';
|
||||
import { getAlarmColumns } from './add-alarm/config';
|
||||
import { IMonitorStrategies } from 'types/base-type';
|
||||
import { pagination } from 'constants/table';
|
||||
import { urlPrefix } from 'constants/left-menu';
|
||||
import { alarm } from 'store/alarm';
|
||||
import 'styles/table-filter.less';
|
||||
|
||||
@observer
|
||||
export class AlarmList extends SearchAndFilterContainer {
|
||||
public state = {
|
||||
searchKey: '',
|
||||
};
|
||||
|
||||
public getData<T extends IMonitorStrategies>(origin: T[]) {
|
||||
let data: T[] = [];
|
||||
let { searchKey } = this.state;
|
||||
searchKey = (searchKey + '').trim().toLowerCase();
|
||||
|
||||
if (app.active !== '-1' || searchKey !== '') {
|
||||
data = origin.filter(d =>
|
||||
((d.name !== undefined && d.name !== null) && d.name.toLowerCase().includes(searchKey as string)
|
||||
|| ((d.operator !== undefined && d.operator !== null) && d.operator.toLowerCase().includes(searchKey as string)))
|
||||
&& (app.active === '-1' || d.appId === (app.active + '')),
|
||||
);
|
||||
} else {
|
||||
data = origin;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public renderTableList(data: IMonitorStrategies[]) {
|
||||
return (
|
||||
<Table
|
||||
rowKey="key"
|
||||
columns={getAlarmColumns(urlPrefix)}
|
||||
dataSource={data}
|
||||
pagination={pagination}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public renderTable() {
|
||||
return this.renderTableList(this.getData(alarm.monitorStrategies));
|
||||
}
|
||||
|
||||
public renderOperationPanel() {
|
||||
return (
|
||||
<>
|
||||
{this.renderApp('应用:')}
|
||||
{this.renderSearch('名称:', '请输入告警名称或者操作人')}
|
||||
<li className="right-btn-1">
|
||||
<Button type="primary">
|
||||
<a href={`${urlPrefix}/alarm/add`}>
|
||||
新建告警
|
||||
</a>
|
||||
</Button>
|
||||
</li>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (!alarm.monitorStrategies.length) {
|
||||
alarm.getMonitorStrategies();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="table-operation-panel">
|
||||
<ul>
|
||||
{this.renderOperationPanel()}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="table-wrapper">
|
||||
{this.renderTable()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
2
kafka-manager-console/src/container/alarm/index.tsx
Normal file
2
kafka-manager-console/src/container/alarm/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './alarm-list';
|
||||
export * from './add-alarm';
|
||||
Reference in New Issue
Block a user