diff --git a/src/legacy/ui/public/agg_types/__tests__/directives/validate_cidr_mask.js b/src/legacy/ui/public/agg_types/__tests__/directives/validate_cidr_mask.js
deleted file mode 100644
index 3d5d2531ee85f..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/directives/validate_cidr_mask.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import '../../directives/validate_cidr_mask';
-
-
-describe('Validate CIDR mask directive', function () {
- let $compile;
- let $rootScope;
- const html = '';
-
- beforeEach(ngMock.module('kibana'));
-
- beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) {
- $compile = _$compile_;
- $rootScope = _$rootScope_;
- }));
-
- it('should allow empty input', function () {
- const element = $compile(html)($rootScope);
-
- $rootScope.value = '';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = null;
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = undefined;
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
- });
-
- it('should allow valid CIDR masks', function () {
- const element = $compile(html)($rootScope);
-
- $rootScope.value = '0.0.0.0/1';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = '128.0.0.1/31';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = '1.2.3.4/2';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = '67.129.65.201/27';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
- });
-
- it('should disallow invalid CIDR masks', function () {
- const element = $compile(html)($rootScope);
-
- $rootScope.value = 'hello, world';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '0.0.0.0';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '0.0.0.0/0';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '0.0.0.0/33';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '256.0.0.0/32';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '0.0.0.0/32/32';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '1.2.3/1';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/directives/validate_ip.js b/src/legacy/ui/public/agg_types/__tests__/directives/validate_ip.js
deleted file mode 100644
index ad4c91e9081bf..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/directives/validate_ip.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import '../../directives/validate_ip';
-
-
-describe('Validate IP directive', function () {
- let $compile;
- let $rootScope;
- const html = '';
-
- beforeEach(ngMock.module('kibana'));
-
- beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) {
- $compile = _$compile_;
- $rootScope = _$rootScope_;
- }));
-
- it('should allow empty input', function () {
- const element = $compile(html)($rootScope);
-
- $rootScope.value = '';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = null;
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = undefined;
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
- });
-
- it('should allow valid IP addresses', function () {
- const element = $compile(html)($rootScope);
-
- $rootScope.value = '0.0.0.0';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = '0.0.0.1';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = '126.45.211.34';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
-
- $rootScope.value = '255.255.255.255';
- $rootScope.$digest();
- expect(element.hasClass('ng-valid')).to.be.ok();
- });
-
- it('should disallow invalid IP addresses', function () {
- const element = $compile(html)($rootScope);
-
- $rootScope.value = 'hello, world';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '0.0.0';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '256.0.0.0';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = '-1.0.0.0';
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
-
- $rootScope.value = Number.MAX_VALUE;
- $rootScope.$digest();
- expect(element.hasClass('ng-invalid')).to.be.ok();
- });
-});
diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.js b/src/legacy/ui/public/agg_types/buckets/ip_range.js
index 621e52e1a99ec..023f2d3cdb520 100644
--- a/src/legacy/ui/public/agg_types/buckets/ip_range.js
+++ b/src/legacy/ui/public/agg_types/buckets/ip_range.js
@@ -18,11 +18,10 @@
*/
import _ from 'lodash';
-import '../directives/validate_ip';
-import '../directives/validate_cidr_mask';
import { BucketAggType } from './_bucket_agg_type';
import { createFilterIpRange } from './create_filter/ip_range';
-import ipRangesTemplate from '../controls/ip_ranges.html';
+import { IpRangeTypeParamEditor } from '../controls/ip_range_type';
+import { IpRangesParamEditor } from '../controls/ip_ranges';
import { i18n } from '@kbn/i18n';
export const ipRangeBucketAgg = new BucketAggType({
@@ -52,6 +51,7 @@ export const ipRangeBucketAgg = new BucketAggType({
filterFieldTypes: 'ip'
}, {
name: 'ipRangeType',
+ editorComponent: IpRangeTypeParamEditor,
default: 'fromTo',
write: _.noop
}, {
@@ -66,7 +66,7 @@ export const ipRangeBucketAgg = new BucketAggType({
{ mask: '128.0.0.0/2' }
]
},
- editor: ipRangesTemplate,
+ editorComponent: IpRangesParamEditor,
write: function (aggConfig, output) {
const ipRangeType = aggConfig.params.ipRangeType;
let ranges = aggConfig.params.ranges[ipRangeType];
diff --git a/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx
new file mode 100644
index 0000000000000..99d689199e24c
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/components/from_to_list.tsx
@@ -0,0 +1,124 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { EuiFieldText, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import Ipv4Address from '../../../utils/ipv4_address';
+import { InputList, InputListConfig, InputModel, InputObject, InputItem } from './input_list';
+
+const EMPTY_STRING = '';
+
+export interface FromToObject extends InputObject {
+ from?: string;
+ to?: string;
+}
+
+type FromToModel = InputModel & {
+ from: InputItem;
+ to: InputItem;
+};
+
+interface FromToListProps {
+ list: FromToObject[];
+ showValidation: boolean;
+ onBlur(): void;
+ onChange(list: FromToObject[]): void;
+ setValidity(isValid: boolean): void;
+}
+
+function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) {
+ const fromToListConfig: InputListConfig = {
+ defaultValue: {
+ from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false },
+ to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false },
+ },
+ defaultEmptyValue: {
+ from: { value: EMPTY_STRING, model: EMPTY_STRING, isInvalid: false },
+ to: { value: EMPTY_STRING, model: EMPTY_STRING, isInvalid: false },
+ },
+ validateClass: Ipv4Address,
+ getModelValue: (item: FromToObject) => ({
+ from: {
+ value: item.from || EMPTY_STRING,
+ model: item.from || EMPTY_STRING,
+ isInvalid: false,
+ },
+ to: { value: item.to || EMPTY_STRING, model: item.to || EMPTY_STRING, isInvalid: false },
+ }),
+ getRemoveBtnAriaLabel: (item: FromToModel) =>
+ i18n.translate('common.ui.aggTypes.ipRanges.removeRangeAriaLabel', {
+ defaultMessage: 'Remove the range of {from} to {to}',
+ values: { from: item.from.value || '*', to: item.to.value || '*' },
+ }),
+ onChangeFn: ({ from, to }: FromToModel) => {
+ const result: FromToObject = {};
+ if (from.model) {
+ result.from = from.model;
+ }
+ if (to.model) {
+ result.to = to.model;
+ }
+ return result;
+ },
+ hasInvalidValuesFn: ({ from, to }: FromToModel) => from.isInvalid || to.isInvalid,
+ renderInputRow: (item: FromToModel, index, onChangeValue) => (
+ <>
+
+ {
+ onChangeValue(index, ev.target.value, 'from');
+ }}
+ value={item.from.value}
+ onBlur={onBlur}
+ />
+
+
+ {
+ onChangeValue(index, ev.target.value, 'to');
+ }}
+ value={item.to.value}
+ onBlur={onBlur}
+ />
+
+ >
+ ),
+ validateModel: (validateFn, object: FromToObject, model: FromToModel) => {
+ validateFn(object.from, model.from);
+ validateFn(object.to, model.to);
+ },
+ };
+
+ return ;
+}
+
+export { FromToList };
diff --git a/src/legacy/ui/public/agg_types/controls/components/input_list.tsx b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx
new file mode 100644
index 0000000000000..68960ca396b3d
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/components/input_list.tsx
@@ -0,0 +1,231 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect, Fragment } from 'react';
+import {
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ htmlIdGenerator,
+ EuiButtonEmpty,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+export interface InputListConfig {
+ defaultValue: InputItemModel;
+ defaultEmptyValue: InputItemModel;
+ validateClass: new (value: string) => { toString(): string };
+ getModelValue(item: InputObject): InputItemModel;
+ getRemoveBtnAriaLabel(model: InputModel): string;
+ onChangeFn(model: InputModel): InputObject;
+ hasInvalidValuesFn(model: InputModel): boolean;
+ renderInputRow(
+ model: InputModel,
+ index: number,
+ onChangeFn: (index: number, value: string, modelName: string) => void
+ ): React.ReactNode;
+ validateModel(
+ validateFn: (value: string | undefined, modelObj: InputItem) => void,
+ object: InputObject,
+ model: InputModel
+ ): void;
+}
+interface InputModelBase {
+ id: string;
+}
+export type InputObject = object;
+export interface InputItem {
+ model: string;
+ value: string;
+ isInvalid: boolean;
+}
+
+interface InputItemModel {
+ [model: string]: InputItem;
+}
+
+// InputModel can have the following implementations:
+// for Mask List - { id: 'someId', mask: { model: '', value: '', isInvalid: false }}
+// for FromTo List - { id: 'someId', from: { model: '', value: '', isInvalid: false }, to: { model: '', value: '', isInvalid: false }}
+export type InputModel = InputModelBase & InputItemModel;
+
+interface InputListProps {
+ config: InputListConfig;
+ list: InputObject[];
+ onChange(list: InputObject[]): void;
+ setValidity(isValid: boolean): void;
+}
+
+const generateId = htmlIdGenerator();
+
+function InputList({ config, list, onChange, setValidity }: InputListProps) {
+ const [models, setModels] = useState(
+ list.length
+ ? list.map(
+ item =>
+ ({
+ id: generateId(),
+ ...config.getModelValue(item),
+ } as InputModel)
+ )
+ : [
+ {
+ id: generateId(),
+ ...config.defaultValue,
+ } as InputModel,
+ ]
+ );
+
+ const onUpdate = (modelList: InputModel[]) => {
+ setModels(modelList);
+ onChange(modelList.map(config.onChangeFn));
+ };
+
+ const onChangeValue = (index: number, value: string, modelName: string) => {
+ const range = models[index][modelName];
+ const { model, isInvalid } = validateValue(value);
+ range.value = value;
+ range.model = model;
+ range.isInvalid = isInvalid;
+ onUpdate(models);
+ };
+ const onDelete = (id: string) => {
+ const newArray = models.filter(model => model.id !== id);
+ onUpdate(newArray);
+ };
+
+ const onAdd = () => {
+ const newArray = [
+ ...models,
+ {
+ id: generateId(),
+ ...config.defaultEmptyValue,
+ } as InputModel,
+ ];
+ onUpdate(newArray);
+ };
+
+ const getUpdatedModels = (objList: InputObject[], modelList: InputModel[]) => {
+ if (!objList.length) {
+ return modelList;
+ }
+ return objList.map((item, index) => {
+ const model = modelList[index] || {
+ id: generateId(),
+ ...config.getModelValue(item),
+ };
+
+ config.validateModel(validateItem, item, model);
+
+ return model;
+ });
+ };
+
+ const validateItem = (value: string | undefined, modelObj: InputItem) => {
+ const { model, isInvalid } = validateValue(value);
+ if (value !== modelObj.model) {
+ modelObj.value = model;
+ }
+ modelObj.model = model;
+ modelObj.isInvalid = isInvalid;
+ };
+
+ const validateValue = (inputValue: string | undefined) => {
+ const result = {
+ model: inputValue || '',
+ isInvalid: false,
+ };
+ if (!inputValue) {
+ result.isInvalid = false;
+ return result;
+ }
+ try {
+ const InputObject = config.validateClass;
+ result.model = new InputObject(inputValue).toString();
+ result.isInvalid = false;
+ return result;
+ } catch (e) {
+ result.isInvalid = true;
+ return result;
+ }
+ };
+
+ const hasInvalidValues = (modelList: InputModel[]) => {
+ return !!modelList.find(config.hasInvalidValuesFn);
+ };
+
+ // responsible for discarding changes
+ useEffect(
+ () => {
+ setModels(getUpdatedModels(list, models));
+ },
+ [list]
+ );
+
+ useEffect(
+ () => {
+ setValidity(!hasInvalidValues(models));
+ },
+ [models]
+ );
+
+ // resposible for setting up an initial value when there is no default value
+ useEffect(() => {
+ onChange(models.map(config.onChangeFn));
+ }, []);
+
+ if (!list || !list.length) {
+ return null;
+ }
+
+ return (
+ <>
+ {models.map((item, index) => (
+
+
+ {config.renderInputRow(item, index, onChangeValue)}
+
+ onDelete(item.id)}
+ />
+
+
+
+
+ ))}
+
+
+
+
+
+
+ >
+ );
+}
+
+export { InputList };
diff --git a/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx
new file mode 100644
index 0000000000000..efb5ca7643c93
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/components/mask_list.tsx
@@ -0,0 +1,101 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { EuiFieldText, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { CidrMask } from '../../../utils/cidr_mask';
+import { InputList, InputListConfig, InputObject, InputModel, InputItem } from './input_list';
+
+const EMPTY_STRING = '';
+
+export interface MaskObject extends InputObject {
+ mask?: string;
+}
+
+type MaskModel = InputModel & {
+ mask: InputItem;
+};
+
+interface MaskListProps {
+ list: MaskObject[];
+ showValidation: boolean;
+ onBlur(): void;
+ onChange(list: MaskObject[]): void;
+ setValidity(isValid: boolean): void;
+}
+
+function MaskList({ showValidation, onBlur, ...rest }: MaskListProps) {
+ const maskListConfig: InputListConfig = {
+ defaultValue: {
+ mask: { model: '0.0.0.0/1', value: '0.0.0.0/1', isInvalid: false },
+ },
+ defaultEmptyValue: {
+ mask: { model: EMPTY_STRING, value: EMPTY_STRING, isInvalid: false },
+ },
+ validateClass: CidrMask,
+ getModelValue: (item: MaskObject) => ({
+ mask: {
+ model: item.mask || EMPTY_STRING,
+ value: item.mask || EMPTY_STRING,
+ isInvalid: false,
+ },
+ }),
+ getRemoveBtnAriaLabel: (item: MaskModel) =>
+ item.mask.value
+ ? i18n.translate('common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel', {
+ defaultMessage: 'Remove the CIDR mask value of {mask}',
+ values: { mask: item.mask.value },
+ })
+ : i18n.translate('common.ui.aggTypes.ipRanges.removeEmptyCidrMaskButtonAriaLabel', {
+ defaultMessage: 'Remove the CIDR mask default value',
+ }),
+ onChangeFn: ({ mask }: MaskModel) => {
+ if (mask.model) {
+ return { mask: mask.model };
+ }
+ return {};
+ },
+ hasInvalidValuesFn: ({ mask }) => mask.isInvalid,
+ renderInputRow: ({ mask }: MaskModel, index, onChangeValue) => (
+
+ {
+ onChangeValue(index, ev.target.value, 'mask');
+ }}
+ value={mask.value}
+ onBlur={onBlur}
+ />
+
+ ),
+ validateModel: (validateFn, object: MaskObject, model: MaskModel) => {
+ validateFn(object.mask, model.mask);
+ },
+ };
+
+ return ;
+}
+
+export { MaskList };
diff --git a/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx
new file mode 100644
index 0000000000000..24964785b72df
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/ip_range_type.tsx
@@ -0,0 +1,67 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+
+import { EuiButtonGroup, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { AggParamEditorProps } from '../../vis/editors/default';
+
+enum IpRangeTypes {
+ MASK = 'mask',
+ FROM_TO = 'fromTo',
+}
+
+function IpRangeTypeParamEditor({ agg, value, setValue }: AggParamEditorProps) {
+ const options = [
+ {
+ id: `visEditorIpRangeFromToLabel${agg.id}`,
+ label: i18n.translate('common.ui.aggTypes.ipRanges.fromToButtonLabel', {
+ defaultMessage: 'From/to',
+ }),
+ },
+ {
+ id: `visEditorIpRangeCidrLabel${agg.id}`,
+ label: i18n.translate('common.ui.aggTypes.ipRanges.cidrMasksButtonLabel', {
+ defaultMessage: 'CIDR masks',
+ }),
+ },
+ ];
+
+ const onClick = (optionId: string) => {
+ setValue(optionId === options[0].id ? IpRangeTypes.FROM_TO : IpRangeTypes.MASK);
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export { IpRangeTypeParamEditor, IpRangeTypes };
diff --git a/src/legacy/ui/public/agg_types/controls/ip_ranges.html b/src/legacy/ui/public/agg_types/controls/ip_ranges.html
deleted file mode 100644
index 5450fae62cb19..0000000000000
--- a/src/legacy/ui/public/agg_types/controls/ip_ranges.html
+++ /dev/null
@@ -1,154 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx
new file mode 100644
index 0000000000000..9a1b804432b76
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/ip_ranges.tsx
@@ -0,0 +1,70 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { EuiFormRow } from '@elastic/eui';
+
+import { AggParamEditorProps } from 'ui/vis/editors/default';
+import { FromToList, FromToObject } from './components/from_to_list';
+import { MaskList, MaskObject } from './components/mask_list';
+import { IpRangeTypes } from './ip_range_type';
+interface IpRange {
+ fromTo: FromToObject[];
+ mask: MaskObject[];
+}
+
+function IpRangesParamEditor({
+ agg,
+ value = { fromTo: [] as FromToObject[], mask: [] as MaskObject[] },
+ setTouched,
+ setValue,
+ setValidity,
+ showValidation,
+}: AggParamEditorProps) {
+ const handleChange = (modelName: IpRangeTypes, items: Array) => {
+ setValue({
+ ...value,
+ [modelName]: items,
+ });
+ };
+
+ return (
+
+ {agg.params.ipRangeType === IpRangeTypes.MASK ? (
+ handleChange(IpRangeTypes.MASK, items)}
+ setValidity={setValidity}
+ />
+ ) : (
+ handleChange(IpRangeTypes.FROM_TO, items)}
+ setValidity={setValidity}
+ />
+ )}
+
+ );
+}
+
+export { IpRangesParamEditor };
diff --git a/src/legacy/ui/public/agg_types/directives/validate_cidr_mask.js b/src/legacy/ui/public/agg_types/directives/validate_cidr_mask.js
deleted file mode 100644
index 6e33f8a141865..0000000000000
--- a/src/legacy/ui/public/agg_types/directives/validate_cidr_mask.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { CidrMask } from '../../utils/cidr_mask';
-import { uiModules } from '../../modules';
-
-uiModules.get('kibana').directive('validateCidrMask', function () {
- return {
- restrict: 'A',
- require: 'ngModel',
- scope: {
- 'ngModel': '='
- },
- link: function ($scope, elem, attr, ngModel) {
- ngModel.$parsers.unshift(validateCidrMask);
- ngModel.$formatters.unshift(validateCidrMask);
-
- function validateCidrMask(mask) {
- if (mask == null || mask === '') {
- ngModel.$setValidity('cidrMaskInput', true);
- return null;
- }
-
- try {
- mask = new CidrMask(mask);
- ngModel.$setValidity('cidrMaskInput', true);
- return mask.toString();
- } catch (e) {
- ngModel.$setValidity('cidrMaskInput', false);
- }
- }
- }
- };
-});
diff --git a/src/legacy/ui/public/agg_types/directives/validate_ip.js b/src/legacy/ui/public/agg_types/directives/validate_ip.js
deleted file mode 100644
index a5e8704fb45fc..0000000000000
--- a/src/legacy/ui/public/agg_types/directives/validate_ip.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import Ipv4Address from '../../utils/ipv4_address';
-import { uiModules } from '../../modules';
-
-uiModules
- .get('kibana')
- .directive('validateIp', function () {
- return {
- restrict: 'A',
- require: 'ngModel',
- scope: {
- 'ngModel': '=',
- },
- link: function ($scope, elem, attr, ngModel) {
- function validateIp(ipAddress) {
- if (ipAddress == null || ipAddress === '') {
- ngModel.$setValidity('ipInput', true);
- return null;
- }
-
- try {
- ipAddress = new Ipv4Address(ipAddress);
- ngModel.$setValidity('ipInput', true);
- return ipAddress.toString();
- } catch (e) {
- ngModel.$setValidity('ipInput', false);
- }
- }
-
- // From User
- ngModel.$parsers.unshift(validateIp);
-
- // To user
- ngModel.$formatters.unshift(validateIp);
- }
- };
- });
diff --git a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts b/src/legacy/ui/public/utils/__tests__/cidr_mask.ts
index 5343328e55786..5277344448bd8 100644
--- a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts
+++ b/src/legacy/ui/public/utils/__tests__/cidr_mask.ts
@@ -43,6 +43,8 @@ describe('CidrMask', () => {
expect(() => new CidrMask('0.0.0.0/32/32')).to.throwError();
expect(() => new CidrMask('1.2.3/1')).to.throwError();
+
+ expect(() => new CidrMask('0.0.0.0/123d')).to.throwError();
});
it('should correctly grab IP address and prefix length', () => {
diff --git a/src/legacy/ui/public/utils/cidr_mask.ts b/src/legacy/ui/public/utils/cidr_mask.ts
index 8dee0ab4e0c12..856fe6fd221eb 100644
--- a/src/legacy/ui/public/utils/cidr_mask.ts
+++ b/src/legacy/ui/public/utils/cidr_mask.ts
@@ -35,7 +35,7 @@ export class CidrMask {
}
this.initialAddress = new Ipv4Address(splits[0]);
this.prefixLength = Number(splits[1]);
- if (this.prefixLength < 1 || this.prefixLength > NUM_BITS) {
+ if (isNaN(this.prefixLength) || this.prefixLength < 1 || this.prefixLength > NUM_BITS) {
throwError(mask);
}
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 5500c34b99d81..aa29b8474df72 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -145,19 +145,6 @@
"common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "このフィルターを削除",
"common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "フィルターラベルを切り替える",
"common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "自動スケールヒストグラムバケットから最高値と最低値を取得できません。これによりビジュアライゼーションのパフォーマンスが低下する可能性があります。",
- "common.ui.aggTypes.ipRanges.cidrMask.addRangeButtonLabel": "範囲を追加",
- "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeDescription": "IP 範囲を最低 1 つ指定する必要があります。",
- "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeLabel": "必須:",
- "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR マスク",
- "common.ui.aggTypes.ipRanges.fromLabel": "開始:",
- "common.ui.aggTypes.ipRanges.fromTo.addRangeButtonLabel": "範囲を追加",
- "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeDescription": "IP 範囲を最低 1 つ指定する必要があります。",
- "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeLabel": "必須:",
- "common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel": "CIDR マスクを削除",
- "common.ui.aggTypes.ipRanges.removeRangeAriaLabel": "この範囲を削除",
- "common.ui.aggTypes.ipRanges.toLabel": "終了:",
- "common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel": "CIDR マスクを使用",
- "common.ui.aggTypes.ipRanges.useFromToButtonLabel": "開始/終了を使用",
"common.ui.aggTypes.jsonInputLabel": "JSON インプット",
"common.ui.aggTypes.jsonInputTooltip": "ここに追加された JSON フォーマットのプロパティは、すべてこのセクションの Elasticsearch アグリゲーション定義に融合されます。用語集約における「shard_size」がその例です。",
"common.ui.aggTypes.metricLabel": "メトリック",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 4e53177c5ff9f..6a2170ea5e774 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -144,19 +144,6 @@
"common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "移除此筛选",
"common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "切换筛选标签",
"common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "无法检索最大值和最小值以自动缩放直方图存储桶。这可能会导致可视化性能低下。",
- "common.ui.aggTypes.ipRanges.cidrMask.addRangeButtonLabel": "添加范围",
- "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeDescription": "必须指定至少一个 IP 范围。",
- "common.ui.aggTypes.ipRanges.cidrMask.requiredIpRangeLabel": "必需:",
- "common.ui.aggTypes.ipRanges.cidrMaskLabel": "CIDR 掩码",
- "common.ui.aggTypes.ipRanges.fromLabel": "从",
- "common.ui.aggTypes.ipRanges.fromTo.addRangeButtonLabel": "添加范围",
- "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeDescription": "必须指定至少一个 IP 范围。",
- "common.ui.aggTypes.ipRanges.fromTo.requiredIpRangeLabel": "必需:",
- "common.ui.aggTypes.ipRanges.removeCidrMaskButtonAriaLabel": "移除此 CIDR 掩码",
- "common.ui.aggTypes.ipRanges.removeRangeAriaLabel": "移除此范围",
- "common.ui.aggTypes.ipRanges.toLabel": "到",
- "common.ui.aggTypes.ipRanges.useCidrMasksButtonLabel": "使用 CIDR 掩码",
- "common.ui.aggTypes.ipRanges.useFromToButtonLabel": "使用“从”/“到”",
"common.ui.aggTypes.jsonInputLabel": "JSON 输入",
"common.ui.aggTypes.jsonInputTooltip": "此处以 JSON 格式添加的任何属性将与此部分的 elasticsearch 聚合定义合并。例如,词聚合上的“shard_size”。",
"common.ui.aggTypes.metricLabel": "指标",