diff --git a/ui/src/kapacitor/components/AlertTabs.tsx b/ui/src/kapacitor/components/AlertTabs.tsx index 26a6f885f3..01635e1bf6 100644 --- a/ui/src/kapacitor/components/AlertTabs.tsx +++ b/ui/src/kapacitor/components/AlertTabs.tsx @@ -271,7 +271,7 @@ class AlertTabs extends PureComponent<Props, State> { AlertTypes.kafka )} notify={this.props.notify} - isMultipleConfigsSupported={true} + isMultipleConfigsSupported={this.isMultipleConfigsSupported} onDelete={this.handleDeleteConfig(AlertTypes.kafka)} /> ) @@ -349,19 +349,6 @@ class AlertTabs extends PureComponent<Props, State> { /> ) case AlertTypes.slack: - const hasPagerDuty2: Section = get( - configSections, - AlertTypes.pagerduty2, - undefined - ) - const hasOpsGenie2: Section = get( - configSections, - AlertTypes.opsgenie2, - undefined - ) - // if kapacitor supports pagerduty2 and opsgenie2, its at least v1.5 - const isMultipleConfigsSupported: boolean = - !_.isUndefined(hasPagerDuty2) && !_.isUndefined(hasOpsGenie2) return ( <SlackConfigs configs={this.getSectionElements(configSections, AlertTypes.slack)} @@ -372,7 +359,7 @@ class AlertTabs extends PureComponent<Props, State> { configSections, AlertTypes.slack )} - isMultipleConfigsSupported={isMultipleConfigsSupported} + isMultipleConfigsSupported={this.isMultipleConfigsSupported} /> ) @@ -442,6 +429,23 @@ class AlertTabs extends PureComponent<Props, State> { return _.get(sections, [section, 'elements', '0'], null) } + private get isMultipleConfigsSupported(): boolean { + const {configSections} = this.state + const hasPagerDuty2: Section = get( + configSections, + AlertTypes.pagerduty2, + undefined + ) + const hasOpsGenie2: Section = get( + configSections, + AlertTypes.opsgenie2, + undefined + ) + // if kapacitor supports pagerduty2 and opsgenie2, its at least v1.5 + return + !_.isUndefined(hasPagerDuty2) && !_.isUndefined(hasOpsGenie2) + } + private getSectionElements = ( sections: Sections, section: string @@ -450,7 +454,7 @@ class AlertTabs extends PureComponent<Props, State> { } private getConfigEnabled = (sections: Sections, section: string): boolean => { - if (section === AlertTypes.slack) { + if (section === AlertTypes.slack || section === AlertTypes.kafka) { const configElements: Section[] = get(sections, `${section}.elements`, []) const enabledConfigElements = configElements.filter(e => { const enabled: boolean = get(e, 'options.enabled', false) diff --git a/ui/src/kapacitor/components/config/KafkaConfig.tsx b/ui/src/kapacitor/components/config/KafkaConfig.tsx index 9de77058a8..5d481dffdf 100644 --- a/ui/src/kapacitor/components/config/KafkaConfig.tsx +++ b/ui/src/kapacitor/components/config/KafkaConfig.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent, MouseEvent} from 'react' +import React, {PureComponent, MouseEvent, ChangeEvent} from 'react' import TagInput from 'src/shared/components/TagInput' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -32,13 +32,14 @@ interface Props { ) => void enabled: boolean notify: (message: Notification | NotificationFunc) => void - id: number + id: string onDelete: (specificConfig: string) => void } interface State { currentBrokers: string[] testEnabled: boolean + enabled: boolean } @ErrorHandling @@ -61,6 +62,7 @@ class KafkaConfig extends PureComponent<Props, State> { this.state = { currentBrokers: brokers || [], testEnabled: this.props.enabled, + enabled: get(this.props, 'config.options.enabled', false), } } @@ -76,6 +78,7 @@ class KafkaConfig extends PureComponent<Props, State> { const sslCert = options['ssl-cert'] const sslKey = options['ssl-key'] const insecureSkipVerify = options['insecure-skip-verify'] + const {enabled} = this.state return ( <form onSubmit={this.handleSubmit}> @@ -191,6 +194,17 @@ class KafkaConfig extends PureComponent<Props, State> { </label> </div> </div> + <div className="form-group col-xs-12"> + <div className="form-control-static"> + <input + type="checkbox" + id={`${keyID}-enabled`} + checked={enabled} + onChange={this.handleEnabledChange} + /> + <label htmlFor={`${keyID}-enabled`}>Configuration Enabled</label> + </div> + </div> <div className="form-group form-group-submit col-xs-12 text-center"> <button className="btn btn-primary" @@ -269,6 +283,7 @@ class KafkaConfig extends PureComponent<Props, State> { 'ssl-cert': this.sslCert.value, 'ssl-key': this.sslKey.value, 'insecure-skip-verify': this.insecureSkipVerify.checked, + enabled: this.state.enabled, } if (this.isNewConfig) { @@ -285,6 +300,11 @@ class KafkaConfig extends PureComponent<Props, State> { } } + private handleEnabledChange = (e: ChangeEvent<HTMLInputElement>) => { + this.setState({enabled: e.target.checked}) + this.disableTest() + } + private handleTest = (e: MouseEvent<HTMLButtonElement>): void => { this.props.onTest(e, {id: this.configID}) } diff --git a/ui/src/kapacitor/components/config/KafkaConfigs.tsx b/ui/src/kapacitor/components/config/KafkaConfigs.tsx new file mode 100644 index 0000000000..1ff73c7a12 --- /dev/null +++ b/ui/src/kapacitor/components/config/KafkaConfigs.tsx @@ -0,0 +1,120 @@ +import React, {Component, MouseEvent} from 'react' +import _ from 'lodash' + +import KafkaConfig from 'src/kapacitor/components/config/KafkaConfig' +import {ErrorHandling} from 'src/shared/decorators/errors' + +import {KafkaProperties} from 'src/types/kapacitor' +import {Notification, NotificationFunc} from 'src/types' + +import {get} from 'src/utils/wrappers' + +interface Config { + options: KafkaProperties & { + id: string + } + isNewConfig?: boolean +} + +interface Props { + configs: Config[] + onSave: (properties: KafkaProperties) => void + onDelete: (specificConfig: string) => void + onTest: ( + e: MouseEvent<HTMLButtonElement>, + specificConfigOptions: Partial<KafkaProperties> & {id: string} + ) => void + onEnabled: (specificConfig: string) => boolean + notify: (message: Notification | NotificationFunc) => void + isMultipleConfigsSupported: boolean +} + +interface State { + configs: Config[] +} + +@ErrorHandling +class KafkaConfigs extends Component<Props, State> { + public static getDerivedStateFromProps(nextProps: Props, prevState: State) { + return {...prevState, configs: nextProps.configs} + } + public state: State = {configs: []} + + public render() { + const {onSave, onDelete, onTest, notify} = this.props + + return ( + <div> + {this.configs.map(c => { + const enabled = get(c, 'options.enabled', false) + const id = get(c, 'options.id', '') + return ( + <KafkaConfig + config={c} + onSave={onSave} + onTest={onTest} + onDelete={onDelete} + enabled={enabled} + notify={notify} + key={id} + id={id} + /> + ) + })} + {this.isAddingConfigsAllowed && ( + <div className="form-group col-xs-12 text-center"> + <button + className="btn btn-md btn-default" + onClick={this.handleAddConfig} + > + <span className="icon plus" /> Add Another Config + </button> + </div> + )} + </div> + ) + } + private get configs(): Config[] { + return _.sortBy(this.state.configs, c => { + const id = get<string>(c, 'options.id', '') + const {isNewConfig} = c + if (id === 'default') { + return '' + } + if (isNewConfig) { + return Infinity + } + return id + }) + } + + private get isAddingConfigsAllowed() { + const {isMultipleConfigsSupported} = this.props + const isAllConfigsPersisted = _.every(this.configs, c => !c.isNewConfig) + return isMultipleConfigsSupported && isAllConfigsPersisted + } + + private handleAddConfig = (): void => { + const {configs} = this.state + const newConfig: Config = { + options: { + id: '', + brokers: [], + timeout: '', + 'batch-size': 0, + 'batch-timeout': '', + 'use-ssl': false, + 'ssl-ca': '', + 'ssl-cert': '', + 'ssl-key': '', + 'insecure-skip-verify': false, + enabled: false, + }, + isNewConfig: true, + } + const updatedConfigs = [...configs, newConfig] + this.setState({configs: updatedConfigs}) + } +} + +export default KafkaConfigs diff --git a/ui/src/types/kapacitor.ts b/ui/src/types/kapacitor.ts index 8aa0067015..218a158827 100644 --- a/ui/src/types/kapacitor.ts +++ b/ui/src/types/kapacitor.ts @@ -315,6 +315,7 @@ export interface KafkaProperties { 'ssl-cert': string 'ssl-key': string 'insecure-skip-verify': boolean + enabled: boolean } export interface OpsGenieProperties {