diff --git a/web/src/components/ListView/index.jsx b/web/src/components/ListView/index.jsx
index 175f041d..8732c8bc 100644
--- a/web/src/components/ListView/index.jsx
+++ b/web/src/components/ListView/index.jsx
@@ -531,6 +531,7 @@ const Index = forwardRef((props, ref) => {
) : null}
diff --git a/web/src/components/Overview/Monitor/index.jsx b/web/src/components/Overview/Monitor/index.jsx
index ecbf8a7a..07eec0fc 100644
--- a/web/src/components/Overview/Monitor/index.jsx
+++ b/web/src/components/Overview/Monitor/index.jsx
@@ -13,6 +13,7 @@ import { getTimezone } from "@/utils/utils";
import { getContext } from "@/pages/DataManagement/context";
import { ESPrefix } from "@/services/common";
import CollectStatus from "@/components/CollectStatus";
+import styles from "./index.less"
const { TabPane } = Tabs;
@@ -140,7 +141,7 @@ const Monitor = (props) => {
-
+
{
selectedCluster?.id ? (
<>
@@ -187,58 +188,59 @@ const Monitor = (props) => {
-
- {
- setParam({ ...param, tab: key });
- }}
- tabBarGutter={10}
- destroyInactiveTabPane
- animated={false}
- >
- {panes.map((pane) => (
-
-
-
-
-
- {checkPaneParams({
- ...state,
- ...extraParams,
- }) ? (
- typeof pane.component == "string" ? (
- pane.component
- ) : (
-
{
- onTimeSettingsChange({
- timeInterval,
- })
- setState({
- ...state,
- timeInterval,
- });
- }}
- setSpinning={setSpinning}
- {...extraParams}
- bucketSize={state.timeInterval}
- />
- )
- ) : null}
-
-
- ))}
-
+
+
{
+ setParam({ ...param, tab: key });
+ }}
+ tabBarGutter={10}
+ destroyInactiveTabPane
+ animated={false}
+ >
+ {panes.map((pane) => (
+
+
+
+
+
+ {checkPaneParams({
+ ...state,
+ ...extraParams,
+ }) ? (
+ typeof pane.component == "string" ? (
+ pane.component
+ ) : (
+
{
+ onTimeSettingsChange({
+ timeInterval,
+ })
+ setState({
+ ...state,
+ timeInterval,
+ });
+ }}
+ setSpinning={setSpinning}
+ {...extraParams}
+ bucketSize={state.timeInterval}
+ />
+ )
+ ) : null}
+
+
+ ))}
+
+
>
) :
}
diff --git a/web/src/components/Overview/Monitor/index.less b/web/src/components/Overview/Monitor/index.less
new file mode 100644
index 00000000..8d95fe64
--- /dev/null
+++ b/web/src/components/Overview/Monitor/index.less
@@ -0,0 +1,7 @@
+.tabs {
+ :global {
+ .ant-tabs .ant-tabs-right-content {
+ padding-right: 16px !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/components/vendor/core/public/saved_objects/simple_saved_object.ts b/web/src/components/vendor/core/public/saved_objects/simple_saved_object.ts
index 7d94afaa..4aa5fcf0 100644
--- a/web/src/components/vendor/core/public/saved_objects/simple_saved_object.ts
+++ b/web/src/components/vendor/core/public/saved_objects/simple_saved_object.ts
@@ -43,7 +43,7 @@ export class SimpleSavedObject {
constructor(
private client: SavedObjectsClientContract,
- { id, type, version, attributes, error, references, migrationVersion }: SavedObjectType
+ { id, type, version, attributes, error, references, migrationVersion, complex_fields }: SavedObjectType
) {
this.id = id;
this.type = type;
@@ -51,6 +51,7 @@ export class SimpleSavedObject {
this.references = references || [];
this._version = version;
this.migrationVersion = migrationVersion;
+ this.attributes.complexFields = complex_fields
if (error) {
this.error = error;
}
diff --git a/web/src/components/vendor/data/common/index_patterns/fields/field_list.ts b/web/src/components/vendor/data/common/index_patterns/fields/field_list.ts
index c0eb55a1..0187d967 100644
--- a/web/src/components/vendor/data/common/index_patterns/fields/field_list.ts
+++ b/web/src/components/vendor/data/common/index_patterns/fields/field_list.ts
@@ -57,7 +57,7 @@ export const fieldList = (
}
this.groups.get(field.type)!.set(field.name, field);
};
- private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name);
+ private removeByGroup = (field: IFieldType) => this.groups.get(field.type)?.delete(field.name);
private calcDisplayName = (name: string) =>
shortDotsEnable ? shortenDottedString(name) : name;
constructor() {
@@ -71,7 +71,7 @@ export const fieldList = (
...(this.groups.get(type) || new Map()).values(),
];
public readonly add = (field: FieldSpec) => {
- const newField = new IndexPatternField(field, this.calcDisplayName(field.name));
+ const newField = new IndexPatternField(field, this.calcDisplayName(field.displayName || field.name));
this.push(newField);
this.setByName(newField);
this.setByGroup(newField);
diff --git a/web/src/components/vendor/data/common/index_patterns/fields/index_pattern_field.ts b/web/src/components/vendor/data/common/index_patterns/fields/index_pattern_field.ts
index 4a22508f..35b989fa 100644
--- a/web/src/components/vendor/data/common/index_patterns/fields/index_pattern_field.ts
+++ b/web/src/components/vendor/data/common/index_patterns/fields/index_pattern_field.ts
@@ -134,6 +134,14 @@ export class IndexPatternField implements IFieldType {
return this.aggregatable && !notVisualizableFieldTypes.includes(this.spec.type);
}
+ public set metric_config(metric_config) {
+ this.spec.metric_config = metric_config;
+ }
+
+ public get metric_config() {
+ return this.spec.metric_config;
+ }
+
public toJSON() {
return {
count: this.count,
@@ -148,7 +156,8 @@ export class IndexPatternField implements IFieldType {
searchable: this.searchable,
aggregatable: this.aggregatable,
readFromDocValues: this.readFromDocValues,
- subType: this.subType,
+ subType: this.subType,
+ metric_config: this.metric_config,
};
}
@@ -171,6 +180,7 @@ export class IndexPatternField implements IFieldType {
readFromDocValues: this.readFromDocValues,
subType: this.subType,
format: getFormatterForField ? getFormatterForField(this).toJSON() : undefined,
+ metric_config: this.metric_config,
};
}
}
diff --git a/web/src/components/vendor/data/common/index_patterns/index_patterns/index_pattern.ts b/web/src/components/vendor/data/common/index_patterns/index_patterns/index_pattern.ts
index 9050b2ae..6f340963 100644
--- a/web/src/components/vendor/data/common/index_patterns/index_patterns/index_pattern.ts
+++ b/web/src/components/vendor/data/common/index_patterns/index_patterns/index_pattern.ts
@@ -117,7 +117,14 @@ export class IndexPattern implements IIndexPattern {
// set values
this.id = spec.id;
- const fieldFormatMap = this.fieldSpecsToFieldFormatMap(spec.fields);
+
+ this.complexFields = fieldList([], this.shortDotsEnable);
+ this.complexFields.replaceAll(this.complexFieldsToArray(spec.complexFields));
+
+ const fieldFormatMap = {
+ ...this.fieldSpecsToFieldFormatMap(spec.fields),
+ ...this.complexfieldSpecsToFieldFormatMap(spec.complexFields)
+ }
this.version = spec.version;
@@ -185,6 +192,31 @@ export class IndexPattern implements IIndexPattern {
{}
);
+ private complexfieldSpecsToFieldFormatMap = (
+ fldList: IndexPatternSpec["fields"] = {}
+ ) =>
+ Object.entries(fldList).reduce>(
+ (col, [key, fieldSpec]) => {
+ if (fieldSpec.format) {
+ col[key] = { ...fieldSpec.format };
+ }
+ return col;
+ },
+ {}
+ );
+
+ private complexFieldsToArray = (complexFields) => {
+ const keys = Object.keys(complexFields || {})
+ return keys.map((key) => {
+ const item = complexFields?.[key] || {}
+ return {
+ ...item,
+ name: key,
+ metric_name: item.name
+ }
+ })
+ };
+
getComputedFields() {
const scriptFields: any = {};
if (!this.fields) {
@@ -381,6 +413,20 @@ export class IndexPattern implements IIndexPattern {
? undefined
: JSON.stringify(serialized);
+ let formatComplexFields
+ if (this.complexFields) {
+ formatComplexFields = {}
+ this.complexFields.map((item) => {
+ if (item.spec?.name) {
+ const { metric_name, format, type, ...rest } = item.spec
+ formatComplexFields[item.spec.name] = {
+ ...rest,
+ name: metric_name
+ }
+ }
+ })
+ }
+
return {
title: this.title,
viewName: this.viewName,
@@ -390,6 +436,7 @@ export class IndexPattern implements IIndexPattern {
? JSON.stringify(this.sourceFilters)
: undefined,
fields: this.fields ? JSON.stringify(this.fields) : undefined,
+ complex_fields: formatComplexFields ? JSON.stringify(formatComplexFields) : undefined,
fieldFormatMap,
type: this.type,
typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined,
diff --git a/web/src/components/vendor/data/common/index_patterns/index_patterns/index_patterns.ts b/web/src/components/vendor/data/common/index_patterns/index_patterns/index_patterns.ts
index 4e63f4a0..f85d8085 100644
--- a/web/src/components/vendor/data/common/index_patterns/index_patterns/index_patterns.ts
+++ b/web/src/components/vendor/data/common/index_patterns/index_patterns/index_patterns.ts
@@ -358,6 +358,7 @@ export class IndexPatternsService {
sourceFilters,
fieldFormatMap,
typeMeta,
+ complexFields,
},
type,
} = savedObject;
@@ -370,6 +371,7 @@ export class IndexPatternsService {
? JSON.parse(fieldFormatMap)
: {};
const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : [];
+ const parsedComplexFields = complexFields ? JSON.parse(complexFields) : [];
this.addFormatsToFields(parsedFields, parsedFieldFormatMap);
return {
@@ -383,6 +385,7 @@ export class IndexPatternsService {
fields: this.fieldArrayToMap(parsedFields),
typeMeta: parsedTypeMeta,
type,
+ complexFields: parsedComplexFields
};
};
@@ -409,7 +412,6 @@ export class IndexPatternsService {
// if (!savedObject.version) {
// throw new SavedObjectNotFound(savedObjectType, id, 'management/kibana/indexPatterns');
// }
-
const spec = this.savedObjectToSpec(savedObject);
const { title, type, typeMeta } = spec;
const parsedFieldFormats: FieldFormatMap = savedObject.attributes
@@ -512,7 +514,6 @@ export class IndexPatternsService {
UI_SETTINGS.SHORT_DOTS_ENABLE
);
const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS);
-
const indexPattern = new IndexPattern({
spec,
savedObjectsClient: this.savedObjectsClient,
@@ -623,8 +624,12 @@ export class IndexPatternsService {
version: indexPattern.version,
})
.then((resp) => {
- indexPattern.id = resp.id;
- indexPattern.version = resp.version;
+ if (resp.id) {
+ indexPattern.id = resp.id;
+ }
+ if (resp.version) {
+ indexPattern.version = resp.version;
+ }
})
.catch(async (err) => {
if (
diff --git a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/constants.ts b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/constants.ts
index 56da031e..a0354999 100644
--- a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/constants.ts
+++ b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/constants.ts
@@ -18,5 +18,6 @@
*/
export const TAB_INDEXED_FIELDS = 'indexedFields';
+export const TAB_COMPLEX_FIELDS = 'complexFields';
export const TAB_SCRIPTED_FIELDS = 'scriptedFields';
export const TAB_SOURCE_FILTERS = 'sourceFilters';
diff --git a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/create_edit_complex_field.tsx b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/create_edit_complex_field.tsx
new file mode 100644
index 00000000..b75c4be8
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/create_edit_complex_field.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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 { withRouter, RouteComponentProps } from "react-router-dom";
+
+import { EuiFlexGroup, EuiFlexItem, EuiPanel } from "@elastic/eui";
+// import { IndexPattern, IndexPatternField } from '../../../../../../plugins/data/public';
+// import { useKibana } from '../../../../../../plugins/kibana_react/public';
+// import { IndexPatternManagmentContext } from '../../../types';
+import { IndexHeader } from "../index_header";
+import { TAB_SCRIPTED_FIELDS, TAB_INDEXED_FIELDS, TAB_COMPLEX_FIELDS } from "../constants";
+
+import { ComplexFieldEditor } from "../../field_editor/complex_field_editor";
+import { useGlobalContext } from "../../../context";
+import { IndexPattern, IndexPatternField } from "../../../import";
+
+interface CreateEditFieldProps extends RouteComponentProps {
+ indexPattern: IndexPattern;
+ mode?: string;
+ fieldName?: string;
+}
+
+export const CreateEditComplexField = withRouter(
+ ({ indexPattern, mode, fieldName, history }: CreateEditFieldProps) => {
+ const { uiSettings, data } = useGlobalContext();
+ const spec =
+ mode === "edit" && fieldName
+ ? indexPattern.complexFields.getByName(fieldName)?.spec
+ : undefined;
+
+ const url = `/patterns/${indexPattern.id}?_a=(tab:complexFields)`;
+
+ if (mode === "edit" && !spec) {
+ history.push(url);
+ }
+
+ const redirectAway = () => {
+ history.push(url);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+);
diff --git a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/create_edit_complex_field_container.tsx b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/create_edit_complex_field_container.tsx
new file mode 100644
index 00000000..84c2f576
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/create_edit_complex_field_container.tsx
@@ -0,0 +1,64 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+import { withRouter, RouteComponentProps } from 'react-router-dom';
+
+// import { IndexPattern } from '../../../../../../plugins/data/public';
+// import { getEditFieldBreadcrumbs, getCreateFieldBreadcrumbs } from '../../breadcrumbs';
+// import { useKibana } from '../../../../../../plugins/kibana_react/public';
+// import { IndexPatternManagmentContext } from '../../../types';
+import { CreateEditComplexField } from './create_edit_complex_field';
+import { IndexPattern } from '../../../import';
+import { useGlobalContext } from '../../../context';
+
+export type CreateEditFieldContainerProps = RouteComponentProps<{ id: string; fieldName: string }>;
+
+const CreateEditFieldCont: React.FC = ({ ...props }) => {
+ // const { setBreadcrumbs, data } = useKibana().services;
+ const {data} = useGlobalContext()
+ const [indexPattern, setIndexPattern] = useState();
+
+ useEffect(() => {
+ data.indexPatterns.get(props.match.params.id).then((ip: IndexPattern) => {
+ setIndexPattern(ip);
+ // if (ip) {
+ // setBreadcrumbs(
+ // props.match.params.fieldName
+ // ? getEditFieldBreadcrumbs(ip, props.match.params.fieldName)
+ // : getCreateFieldBreadcrumbs(ip)
+ // );
+ // }
+ });
+ }, [props.match.params.id, props.match.params.fieldName, data.indexPatterns]);//setBreadcrumbs
+
+ if (indexPattern) {
+ return (
+
+ );
+ } else {
+ return <>>;
+ }
+};
+
+export const CreateEditComplexFieldContainer = withRouter(CreateEditFieldCont);
diff --git a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/index.ts b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/index.ts
new file mode 100644
index 00000000..8b31ec2b
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field/index.ts
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+export { CreateEditComplexField } from './create_edit_complex_field';
+export { CreateEditComplexFieldContainer } from './create_edit_complex_field_container';
diff --git a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/complex_fields_table.jsx b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/complex_fields_table.jsx
new file mode 100644
index 00000000..79564cc2
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/complex_fields_table.jsx
@@ -0,0 +1,164 @@
+import React, { Component } from "react";
+import { createSelector } from "reselect";
+import { Table } from "./components/table";
+import { getFieldFormat } from "./lib";
+import { IndexPatternField, IndexPattern, IFieldType } from "../../../import";
+import { EuiContext } from "@elastic/eui";
+import { formatMessage } from "umi/locale";
+import { router } from "umi";
+
+export class ComplexFieldsTable extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ fields: this.mapFields(this.props.indexPattern?.complexFields),
+ };
+ }
+
+ UNSAFE_componentWillReceiveProps(nextProps) {
+ if (nextProps.indexPattern?.complexFields !== this.props.indexPattern?.complexFields) {
+ this.setState({
+ fields: this.mapFields(nextProps.indexPattern?.complexFields),
+ });
+ }
+ }
+
+ mapFields(fields) {
+ const { indexPattern, fieldWildcardMatcher, helpers } = this.props;
+ const sourceFilters =
+ indexPattern.sourceFilters &&
+ indexPattern.sourceFilters.map((f) => f.value);
+ const fieldWildcardMatch = fieldWildcardMatcher ? fieldWildcardMatcher(sourceFilters || []) : undefined;
+ return (
+ (fields &&
+ fields.map((field) => {
+ const func = Object.keys(field.spec?.function || {})[0]
+ return {
+ ...field.spec,
+ displayName: field.spec.metric_name,
+ func: func,
+ format: getFieldFormat(indexPattern, field.name),
+ excluded: fieldWildcardMatch
+ ? fieldWildcardMatch(field.name)
+ : false,
+ info:
+ helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field),
+ };
+ })) ||
+ []
+ );
+ }
+
+ getFilteredFields = createSelector(
+ (state) => state.fields,
+ (state, props) =>
+ props.fieldFilter,
+ (state, props) =>
+ props.indexedFieldTypeFilter,
+ (fields, fieldFilter, indexedFieldTypeFilter) => {
+ if (fieldFilter) {
+ const normalizedFieldFilter = fieldFilter.toLowerCase();
+ fields = fields.filter((field) =>
+ field.name.toLowerCase().includes(normalizedFieldFilter)
+ );
+ }
+
+ if (indexedFieldTypeFilter) {
+ fields = fields.filter(
+ (field) => field.type === indexedFieldTypeFilter
+ );
+ }
+
+ return fields;
+ }
+ );
+
+ render() {
+ const { indexPattern } = this.props;
+ const fields = this.getFilteredFields(this.state, {...this.props, fields: indexPattern?.complexFields});
+ const editField = (field) => this.props.helpers.redirectToRoute(field)
+
+ return (
+
+
+ value,
+ 'data-test-subj': 'complexFieldName',
+ },
+ {
+ field: 'func',
+ name: 'Function',
+ dataType: 'string',
+ sortable: true,
+ render: (value) => value ? value.toUpperCase() : '-',
+ 'data-test-subj': 'complexFieldFunc',
+ },
+ {
+ field: 'format',
+ name: 'Format',
+ dataType: 'string',
+ sortable: true,
+ render: (value) => value || 'Number',
+ 'data-test-subj': 'complexFieldFormat',
+ },
+ {
+ field: 'tags',
+ name: 'Tags',
+ dataType: 'auto',
+ render: (value) => {
+ return value?.join(', ');
+ },
+ 'data-test-subj': 'complexFieldTags',
+ },
+ {
+ field: "builtin",
+ name: 'Builtin',
+ dataType: 'auto',
+ render: (value) => {
+ return value === true ? "true" : "false";
+ },
+ 'data-test-subj': 'complexFieldBuiltin',
+ },
+ {
+ name: '',
+ actions: [
+ {
+ name: 'Edit',
+ description: 'Edit',
+ icon: 'pencil',
+ onClick: editField,
+ type: 'icon',
+ 'data-test-subj': 'editFieldFormat',
+ available: ({ builtin }) => !builtin,
+ },
+ ],
+ width: '40px',
+ },
+ ]}
+ />
+
+
+ );
+ }
+}
diff --git a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx
index fc8320c1..35d2888c 100644
--- a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx
+++ b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx
@@ -139,7 +139,7 @@ export class Table extends PureComponent {
pageSizeOptions: [5, 10, 25, 50],
};
- const columns: Array> = [
+ const columns: Array> = this.props.columns || [
{
field: 'displayName',
name: nameHeader,
diff --git a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx
index 18701556..c57ed6f8 100644
--- a/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx
+++ b/web/src/components/vendor/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx
@@ -34,6 +34,7 @@ import {
EuiFieldSearch,
EuiSelect,
EuiSelectOption,
+ EuiButton,
} from "@elastic/eui";
// import { fieldWildcardMatcher } from '../../../../../utils/public';
import {
@@ -46,6 +47,7 @@ import {
// import { IndexPatternManagmentContext } from '../../../types';
import { createEditIndexPatternPageStateContainer } from "../edit_index_pattern_state_container";
import {
+ TAB_COMPLEX_FIELDS,
TAB_INDEXED_FIELDS,
TAB_SCRIPTED_FIELDS,
TAB_SOURCE_FILTERS,
@@ -64,6 +66,7 @@ import {
import { useGlobalContext } from "../../../context";
import LayoutList from "@/pages/DataManagement/View/LayoutList"
+import { ComplexFieldsTable } from "../indexed_fields_table/complex_fields_table";
interface TabsProps extends Pick {
indexPattern: IndexPattern;
@@ -95,6 +98,7 @@ export function Tabs({
indexPatternFieldEditor,
} = useGlobalContext();
const [fieldFilter, setFieldFilter] = useState("");
+ const [complexFieldFilter, setComplexFieldFilter] = useState("");
const [indexedFieldTypeFilter, setIndexedFieldTypeFilter] = useState(
""
);
@@ -193,6 +197,39 @@ export function Tabs({
]
);
+ const getComplexFilterSection = useCallback(
+ () => {
+ return (
+
+
+ setComplexFieldFilter(e.target.value)}
+ data-test-subj="complexFieldFilter"
+ aria-label={searchAriaLabel}
+ />
+
+
+ {
+ history.push(`/patterns/${indexPattern?.id}/complex/create`);
+ }}
+ >
+ {"Create field"}
+
+
+
+ );
+ },
+ [
+ complexFieldFilter,
+ indexPattern,
+ ]
+ );
+
const getContent = useCallback(
(type: string) => {
switch (type) {
@@ -217,6 +254,25 @@ export function Tabs({
/>
);
+ case TAB_COMPLEX_FIELDS:
+ return (
+
+
+ {getComplexFilterSection()}
+
+ {
+ history.push(`/patterns/${indexPattern?.id}/complex/${field?.name}/edit`);
+ },
+ getFieldInfo: indexPatternManagementStart.list.getFieldInfo,
+ }}
+ />
+
+ );
case TAB_SCRIPTED_FIELDS:
return (
@@ -261,6 +317,7 @@ export function Tabs({
fieldWildcardMatcherDecorated,
fields,
getFilterSection,
+ getComplexFilterSection,
history,
indexPattern,
indexPatternManagementStart.list.getFieldInfo,
@@ -272,20 +329,30 @@ export function Tabs({
);
const euiTabs: EuiTabbedContentTab[] = useMemo(
- () =>
- getTabs(indexPattern, fieldFilter, indexPatternManagementStart.list).map(
+ () => {
+ const tabs = getTabs(indexPattern, fieldFilter, indexPatternManagementStart.list).map(
(tab: Pick) => {
return {
...tab,
content: getContent(tab.id),
};
}
- ).concat([{
- name: 'Layout',
- id: 'layout',
- content: ,
- }]),
- [fieldFilter, getContent, indexPattern, indexPatternManagementStart.list]
+ )
+ let count = indexPattern?.complexFields?.length || 0
+ if (complexFieldFilter) {
+ const normalizedFieldFilter = complexFieldFilter.toLowerCase();
+ const fields = indexPattern?.complexFields?.filter((field) =>
+ field.name.toLowerCase().includes(normalizedFieldFilter)
+ );
+ count = fields.length
+ }
+ return tabs.concat([{
+ name: `Complex fields (${count})`,
+ id: TAB_COMPLEX_FIELDS,
+ content: getContent(TAB_COMPLEX_FIELDS)
+ }])
+ },
+ [fieldFilter, getContent, indexPattern, indexPatternManagementStart.list, complexFieldFilter]
);
const [selectedTabId, setSelectedTabId] = useState(euiTabs[0].id);
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/complex_field_editor.less b/web/src/components/vendor/index_pattern_management/public/components/field_editor/complex_field_editor.less
new file mode 100644
index 00000000..aab55d3d
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/complex_field_editor.less
@@ -0,0 +1,16 @@
+.editor {
+ background: #fff;
+ :global {
+ .euiComboBox__inputWrap .euiBadge {
+ background: #fcfbfd;
+ color: #343741;
+ font-weight: 400;
+ font-size: 14px;
+ padding: 0;
+ border: 0;
+ }
+ .euiBadge__content {
+ font-weight: 400;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/complex_field_editor.tsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/complex_field_editor.tsx
new file mode 100644
index 00000000..896c943e
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/complex_field_editor.tsx
@@ -0,0 +1,552 @@
+/*
+ * 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, { PureComponent, Fragment, useState, useCallback } from 'react';
+import { intersection, union, get, cloneDeep } from 'lodash';
+
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiCode,
+ EuiConfirmModal,
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiForm,
+ EuiFormRow,
+ EuiIcon,
+ EuiOverlayMask,
+ EuiSelect,
+ EuiSpacer,
+ EuiText,
+ EUI_MODAL_CONFIRM_BUTTON,
+ EuiBadge,
+} from '@elastic/eui';
+
+import {
+ IndexPatternField,
+ FieldFormatInstanceType,
+ IndexPattern,
+ IFieldType,
+ KBN_FIELD_TYPES,
+ ES_FIELD_TYPES,
+ DataPublicPluginStart,
+} from '../../../../../kibana/data/public';
+
+import { FieldFormatEditor } from './components/field_format_editor';
+import { IndexPatternManagmentContextValue } from '../../types';
+
+import { FIELD_TYPES_BY_LANG, DEFAULT_FIELD_TYPES } from './constants';
+
+// This loads Ace editor's "groovy" mode, used below to highlight the script.
+import 'brace/mode/groovy';
+import { useGlobalContext } from '../../context';
+import { formatMessage } from "umi/locale";
+import { message } from 'antd';
+import functions from './functions';
+import styles from './complex_field_editor.less'
+import { generate20BitUUID } from '@/utils/utils';
+import { Tags } from './field_editor';
+
+const getFieldTypeFormatsList = (
+ field: IndexPatternField['spec'],
+ defaultFieldFormat: FieldFormatInstanceType,
+ fieldFormats: DataPublicPluginStart['fieldFormats']
+) => {
+ const formatsByType = fieldFormats
+ .getByFieldType(field.type as KBN_FIELD_TYPES)
+ .map(({ id, title }) => ({
+ id,
+ title,
+ })).filter((item) => ['number', 'bytes', 'percent'].includes(item.id));
+
+ return [
+ {
+ id: '',
+ defaultFieldFormat,
+ title: '- Default -',
+ },
+ ...formatsByType,
+ ];
+};
+
+interface FieldTypeFormat {
+ id: string;
+ title: string;
+}
+
+interface InitialFieldTypeFormat extends FieldTypeFormat {
+ defaultFieldFormat: FieldFormatInstanceType;
+}
+
+export interface FieldEditorState {
+ isReady: boolean;
+ isCreating: boolean;
+ isDeprecatedLang: boolean;
+ fieldTypes: string[];
+ fieldTypeFormats: FieldTypeFormat[];
+ existingFieldNames: string[];
+ fieldFormatId?: string;
+ fieldFormatParams: { [key: string]: unknown };
+ showDeleteModal: boolean;
+ hasFormatError: boolean;
+ isSaving: boolean;
+ errors?: string[];
+ format: any;
+ spec: IndexPatternField['spec'];
+}
+
+export interface FieldEdiorProps {
+ indexPattern: IndexPattern;
+ spec: IndexPatternField['spec'];
+ services: {
+ redirectAway: () => void;
+ saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject'];
+ };
+}
+
+const { data } = useGlobalContext();
+
+export class ComplexFieldEditor extends PureComponent {
+ // static contextType = contextType;
+
+ public readonly context!: IndexPatternManagmentContextValue;
+
+ constructor(props: FieldEdiorProps, context: IndexPatternManagmentContextValue) {
+ super(props, context);
+
+ const { spec, indexPattern } = props;
+
+ const isCreating = !indexPattern.complexFields.getByName(spec.name)
+ const initSpec = cloneDeep({ ...spec, type: 'number' })
+ if (isCreating) {
+ initSpec['function'] = {
+ 'rate': {}
+ }
+ }
+ const format = props.indexPattern.getFormatterForField(initSpec)
+ const DefaultFieldFormat = data.fieldFormats.getDefaultType(
+ initSpec.type as KBN_FIELD_TYPES,
+ initSpec.esTypes as ES_FIELD_TYPES[]
+ );
+ this.state = {
+ isDeprecatedLang: false,
+ existingFieldNames: indexPattern.complexFields.getAll().map((f: IFieldType) => f.name),
+ showDeleteModal: false,
+ hasFormatError: false,
+ isSaving: false,
+ format: props.indexPattern.getFormatterForField(initSpec),
+ spec: initSpec,
+ isReady: true,
+ isCreating: !indexPattern.complexFields.getByName(initSpec.name),
+ errors: [],
+ fieldTypeFormats: getFieldTypeFormatsList(
+ initSpec,
+ DefaultFieldFormat as FieldFormatInstanceType,
+ data.fieldFormats
+ ),
+ fieldFormatId: get(indexPattern, ['fieldFormatMap', initSpec.name, 'type', 'id']),
+ fieldFormatParams: format?.params(),
+ };
+ }
+
+ onFieldChange = (fieldName: string, value: string | number) => {
+ const { spec } = this.state;
+ (spec as any)[fieldName] = value;
+ this.forceUpdate();
+ };
+
+ onFormatChange = (formatId: string, params?: any) => {
+ const { spec, fieldTypeFormats } = this.state;
+ const { uiSettings, data } = useGlobalContext(); //this.context.services;
+
+ const FieldFormat = data.fieldFormats.getType(
+ formatId || (fieldTypeFormats[0] as InitialFieldTypeFormat).defaultFieldFormat.id
+ ) as FieldFormatInstanceType;
+
+ const newFormat = new FieldFormat(params, (key)=>{})//(key) => uiSettings.get(key));
+ spec.format = newFormat;
+
+ this.setState({
+ fieldFormatId: FieldFormat.id,
+ fieldFormatParams: newFormat.params(),
+ format: newFormat,
+ });
+ };
+
+ onFormatParamsChange = (newParams: { fieldType: string; [key: string]: any }) => {
+ const { fieldFormatId } = this.state;
+ this.onFormatChange(fieldFormatId as string, newParams);
+ };
+
+ onFormatParamsError = (error?: string) => {
+ this.setState({
+ hasFormatError: !!error,
+ });
+ };
+
+ isDuplicateName() {
+ const { isCreating, spec, existingFieldNames } = this.state;
+ return isCreating && existingFieldNames.includes(spec.name);
+ }
+
+ renderName() {
+ const { isCreating, spec } = this.state;
+ const isInvalid = !spec.name || !spec.name.trim();
+
+ return isCreating ? (
+
+
+
+ You already have a field with the name {spec.name}.
+
+ ) : null
+ }
+ isInvalid={isInvalid}
+ error={
+ isInvalid
+ ? 'Name is required'
+ : null
+ }
+ >
+ {
+ this.onFieldChange('name', e.target.value);
+ }}
+ isInvalid={isInvalid}
+ />
+
+ ) : null;
+ }
+
+ renderFormat() {
+ const { spec, fieldTypeFormats, fieldFormatId, fieldFormatParams, format } = this.state;
+ const { indexPatternManagementStart } = useGlobalContext(); //this.context.services;
+ const defaultFormat = (fieldTypeFormats[0] as InitialFieldTypeFormat).defaultFieldFormat.title;
+
+ const label = defaultFormat ? (<>
+ Format (Default: {defaultFormat})>
+ ) : (
+ "Format"
+ );
+
+ return (
+
+
+ {
+ return { value: fmt.id || '', text: fmt.title };
+ })}
+ data-test-subj="editorSelectedFormatId"
+ onChange={(e) => {
+ this.onFormatChange(e.target.value);
+ }}
+ />
+
+ {fieldFormatId ? (
+
+ ) : null}
+
+ );
+ }
+
+ renderDeleteModal = () => {
+ const { spec } = this.state;
+
+ return this.state.showDeleteModal ? (
+
+ {
+ this.hideDeleteModal();
+ this.deleteField();
+ }}
+ cancelButtonText='Cancel'
+ confirmButtonText= 'Delete'
+ buttonColor="danger"
+ defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
+ >
+
+ You can't recover a deleted field.
+
+
+ Are you sure you want to do this?
+
+
+
+ ) : null;
+ };
+
+ showDeleteModal = () => {
+ this.setState({
+ showDeleteModal: true,
+ });
+ };
+
+ hideDeleteModal = () => {
+ this.setState({
+ showDeleteModal: false,
+ });
+ };
+
+ renderActions() {
+ const { isCreating, spec, isSaving } = this.state;
+ const { redirectAway } = this.props.services;
+
+ if (spec?.builtin) {
+ return null
+ }
+
+ return (
+
+
+
+
+ {isCreating ? (
+ "Create field"
+ ) : (
+ "Save field"
+ )}
+
+
+
+
+ Cancel
+
+
+ {!isCreating ? (
+
+
+
+
+ Delete
+
+
+
+
+ ) : null}
+
+
+ );
+ }
+
+ deleteField = () => {
+ const { redirectAway, saveIndexPattern } = this.props.services;
+
+ const field = this.state.spec;
+ const { indexPattern } = this.props;
+
+ const fieldExists = !!indexPattern.complexFields.getByName(field.name);
+
+ if (fieldExists) {
+ indexPattern.complexFields.remove(field);
+ }
+
+ if (indexPattern.fieldFormatMap[field.name]) {
+ indexPattern.fieldFormatMap[field.name] = undefined;
+ }
+
+ saveIndexPattern(indexPattern).then(() => {
+ // const message = `Deleted '${spec.name}'`;
+ // this.context.services.notifications.toasts.addSuccess(message);
+ redirectAway();
+ });
+ };
+
+ saveField = async () => {
+ const field = this.state.spec;
+ const { indexPattern } = this.props;
+ const { fieldFormatId } = this.state;
+
+ const { redirectAway, saveIndexPattern } = this.props.services;
+ const fieldExists = !!indexPattern.complexFields.getByName(field.name);
+
+ let oldField: IndexPatternField['spec'];
+
+ if (fieldExists) {
+ oldField = indexPattern.complexFields.getByName(field.name)!.spec;
+ indexPattern.complexFields.update(field);
+ } else {
+ field.name = generate20BitUUID()
+ indexPattern.complexFields.add(field);
+ }
+
+ if (!fieldFormatId) {
+ indexPattern.fieldFormatMap[field.name] = undefined;
+ } else {
+ indexPattern.fieldFormatMap[field.name] = field.format;
+ }
+
+ return saveIndexPattern(indexPattern)
+ .then(() => {
+ // const message = i18n.translate('indexPatternManagement.deleteField.savedHeader', {
+ // defaultMessage: "Saved '{fieldName}'",
+ // values: { fieldName: field.name },
+ // });
+ // this.context.services.notifications.toasts.addSuccess(message);
+ redirectAway();
+ })
+ .catch(() => {
+ if (oldField) {
+ indexPattern.complexFields.update(oldField);
+ } else {
+ indexPattern.complexFields.remove(field);
+ }
+ });
+ };
+
+ isSavingDisabled() {
+ const { spec, hasFormatError } = this.state;
+
+ if (
+ hasFormatError
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ renderFunction(func) {
+ const { spec } = this.state;
+ const { indexPattern } = this.props;
+ const component = functions[func]
+ const props = {
+ spec,
+ indexPattern,
+ onChange: (value) => {
+ this.onFieldChange('function', value)
+ }
+ }
+ if (component) {
+ return component(props)
+ }
+ }
+
+ renderMetricConfig() {
+ const { spec } = this.state;
+ const keys = Object.keys(spec?.function || {})
+ const statistic = keys[0]
+
+ return (
+ <>
+
+ {
+ this.onFieldChange('metric_name', e.target.value)
+ }}
+ />
+
+
+ ({ value: item, text: item.toUpperCase() }))}
+ value={statistic}
+ onChange={(e) => {
+ this.onFieldChange('function', { [e.target.value]: {} })
+ }}
+ />
+
+ {this.renderFunction(statistic || 'rate')}
+ {this.renderFormat()}
+
+ {
+ this.onFieldChange('unit', e.target.value)
+ }}
+ />
+
+
+ {
+ this.onFieldChange('tags', value)
+ }}/>
+
+ >
+ );
+ }
+
+ render() {
+ const { isReady, isCreating, spec } = this.state;
+
+ return isReady ? (
+
+
+
+ {isCreating ? (
+ "Create field"
+ ) : (
+ `Edit ${spec.metric_name }`
+ )}
+
+
+
+
+ {this.renderMetricConfig()}
+ {this.renderActions()}
+ {this.renderDeleteModal()}
+
+
+
+ ) : null;
+ }
+}
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/field_editor.less b/web/src/components/vendor/index_pattern_management/public/components/field_editor/field_editor.less
new file mode 100644
index 00000000..a181e5c4
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/field_editor.less
@@ -0,0 +1,8 @@
+.editor {
+ background: #fff;
+ :global {
+ .euiBadge__content {
+ font-weight: 400;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/field_editor.tsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/field_editor.tsx
index 7de61d5f..9b374966 100644
--- a/web/src/components/vendor/index_pattern_management/public/components/field_editor/field_editor.tsx
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/field_editor.tsx
@@ -17,8 +17,8 @@
* under the License.
*/
-import React, { PureComponent, Fragment } from 'react';
-import { intersection, union, get } from 'lodash';
+import React, { PureComponent, Fragment, useState, useMemo, useCallback } from 'react';
+import { intersection, union, get, cloneDeep } from 'lodash';
import {
EuiBasicTable,
@@ -41,6 +41,13 @@ import {
EuiSpacer,
EuiText,
EUI_MODAL_CONFIRM_BUTTON,
+ EuiFilterButton,
+ EuiFilterGroup,
+ EuiPopover,
+ EuiSelectable,
+ EuiPopoverTitle,
+ EuiBadge,
+ EuiComboBox,
} from '@elastic/eui';
import {
@@ -69,10 +76,32 @@ import { IndexPatternManagmentContextValue } from '../../types';
import { FIELD_TYPES_BY_LANG, DEFAULT_FIELD_TYPES } from './constants';
import { executeScript, isScriptValid } from './lib';
+import styles from './field_editor.less'
// This loads Ace editor's "groovy" mode, used below to highlight the script.
import 'brace/mode/groovy';
import { useGlobalContext } from '../../context';
+import { formatMessage } from "umi/locale";
+import { message } from 'antd';
+import { getRollupEnabled } from '@/utils/authority';
+
+export const getStatistics = (type) => {
+ if (!type || type === 'string') return ["count", "cardinality"];
+ return [
+ "max",
+ "min",
+ "avg",
+ "sum",
+ "medium",
+ "p99",
+ "p95",
+ "p90",
+ "p80",
+ "p50",
+ "count",
+ "cardinality",
+ ];
+};
const getFieldTypeFormatsList = (
field: IndexPatternField['spec'],
@@ -206,7 +235,7 @@ export class FieldEditor extends PureComponent {
+ const { spec = {} } = this.state;
+ const settings = cloneDeep(spec['metric_config'] || {})
+ settings[key] = value
+ this.onFieldChange('metric_config', settings)
+ };
+
+ renderMetricConfig() {
+ const { spec } = this.state;
+
+ const isRollupEnabled = getRollupEnabled() === 'true'
+
+ return (
+ <>
+
+ {
+ this.onMetricSettingsChange('name', e.target.value)
+ }}
+ />
+
+
+ {
+ this.onMetricSettingsChange('option_aggs', value)
+ }}
+ spec={spec}
+ />
+
+
+
+ {
+ this.onMetricSettingsChange('unit', e.target.value)
+ }}
+ />
+
+
+ {
+ this.onMetricSettingsChange('tags', value)
+ }}/>
+
+ >
+ );
+ }
+
render() {
const { isReady, isCreating, spec } = this.state;
return isReady ? (
-
+
{isCreating ? (
@@ -774,6 +864,7 @@ export class FieldEditor extends PureComponent
@@ -782,3 +873,140 @@ export class FieldEditor extends PureComponent {
+
+ const { value = [], statistics = [], onChange, spec } = props;
+
+ const isRollupEnabled = getRollupEnabled() === 'true'
+
+ const options = useMemo(() => {
+ const limits = ROLLUP_FIELDS[spec?.name] || ['max', 'count']
+ return statistics.map((item) => ({
+ label: item.toUpperCase(), value: item, disabled: isRollupEnabled ? !limits.includes(item) : false
+ }))
+ }, [value, statistics, spec, isRollupEnabled])
+
+
+ return (
+ ({ label: item.toUpperCase(), value: item }))}
+ onChange={(value) => {
+ onChange(value.map((item) => item.value))
+ }}
+ />
+ );
+};
+
+export const Tags = ({ value = [], onChange }) => {
+ const [inputVisible, setInputVisible] = useState(false);
+ const [inputValue, setInputValue] = useState("");
+
+ const handleInputChange = (e) => {
+ setInputValue(e.target.value);
+ };
+
+ const showInput = () => {
+ setInputVisible(true);
+ };
+
+ const handleRemove = useCallback(
+ (index) => {
+ const newValue = [...value];
+ newValue.splice(index, 1);
+ onChange(newValue);
+ },
+ [value]
+ );
+
+ const handleInputConfirm = (input) => {
+ if (input.length === 0) {
+ return message.warning(
+ formatMessage({ id: "command.message.invalid.tag" })
+ );
+ }
+ if (input) onChange([...(value || []), input]);
+ setInputVisible(false);
+ setInputValue("");
+ }
+
+ const handleKeyDown = (event) => {
+ if (event.key === 'Enter') {
+ handleInputConfirm(inputValue)
+ }
+ };
+
+ return (
+
+ {value.map((tag, index) => (
+
+ handleRemove(index)}
+ style={{ height: '40px', lineHeight: '40px', fontSize: 14}}
+ >
+ {tag}
+
+
+
+ ))}
+ {inputVisible && (
+
+ {
+ setInputVisible(false);
+ setInputValue("");
+ }}
+ autoFocus
+ />
+
+ )}
+ {!inputVisible && (
+
+
+ Add New
+
+
+ )}
+
+ );
+};
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/index.jsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/index.jsx
new file mode 100644
index 00000000..f226ed7f
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/index.jsx
@@ -0,0 +1,35 @@
+import rate from './rate'
+import rate_sum_func_value_in_group from './rate_sum_func_value_in_group'
+import latency from './latency'
+import latency_sum_func_value_in_group from './latency_sum_func_value_in_group'
+import sum_func_value_in_group from './sum_func_value_in_group'
+
+export const getStatistics = (type) => {
+ if (type !== 'string') {
+ return [
+ "max",
+ "min",
+ "avg",
+ "sum",
+ "medium",
+ "p99",
+ "p95",
+ "p90",
+ "p80",
+ "p50",
+ "count",
+ "cardinality",
+ ];
+ }
+ return ["count", "cardinality"];
+};
+
+const functions = {
+ rate,
+ rate_sum_func_value_in_group,
+ latency,
+ latency_sum_func_value_in_group,
+ sum_func_value_in_group,
+}
+
+export default functions
\ No newline at end of file
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/latency.jsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/latency.jsx
new file mode 100644
index 00000000..35d28f39
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/latency.jsx
@@ -0,0 +1,46 @@
+import { EuiComboBox, EuiFormRow } from "@elastic/eui"
+
+export default (props) => {
+ const { indexPattern, spec, onChange } = props;
+ const keys = Object.keys(spec?.function || {})
+ const statistic = keys[0]
+ const func = spec?.function?.[statistic]
+ const { divisor, dividend } = func || {}
+
+ return (
+ <>
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name }
+ ))}
+ selectedOptions={dividend ? [{ value: dividend, label: dividend }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ dividend: value[0]?.value
+ }})
+ }}
+ isClearable={false}
+ />
+
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name }
+ ))}
+ selectedOptions={divisor ? [{ value: divisor, label: divisor }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ divisor: value[0]?.value
+ }})
+ }}
+ isClearable={false}
+ />
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/latency_sum_func_value_in_group.jsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/latency_sum_func_value_in_group.jsx
new file mode 100644
index 00000000..ec071073
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/latency_sum_func_value_in_group.jsx
@@ -0,0 +1,90 @@
+import { EuiComboBox, EuiFormRow } from "@elastic/eui"
+import { getStatistics } from ".";
+
+export default (props) => {
+ const { indexPattern, spec, onChange } = props;
+ const keys = Object.keys(spec?.function || {})
+ const statistic = keys[0]
+ const func = spec?.function?.[statistic]
+ const { divisor, dividend, group } = func || {}
+
+ return (
+ <>
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name, type: item.spec?.type }
+ ))}
+ selectedOptions={dividend ? [{ value: dividend, label: dividend }] : []}
+ onChange={(value) => {
+ const types = getStatistics(value[0]?.type)
+ onChange({ [statistic]: {
+ ...(func || {}),
+ dividend: value[0]?.value,
+ group: {
+ ...(func?.group || {}),
+ func: types.includes('max') ? 'max' : types[0]
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name }
+ ))}
+ selectedOptions={divisor ? [{ value: divisor, label: divisor }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ divisor: value[0]?.value
+ }})
+ }}
+ isClearable={false}
+ />
+
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name }
+ ))}
+ selectedOptions={group?.field ? [{ value: group?.field, label: group?.field }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ group: {
+ ...(func?.group || {}),
+ field: value[0]?.value,
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+
+ {/*
+ (
+ { value: item, label: item.toUpperCase() }
+ ))}
+ selectedOptions={group?.func ? [{ value: group?.func, label: group?.func?.toUpperCase() }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ group: {
+ ...(group || {}),
+ func: value[0]?.value
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+ */}
+ >
+ )
+}
\ No newline at end of file
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/rate.jsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/rate.jsx
new file mode 100644
index 00000000..6a3c349e
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/rate.jsx
@@ -0,0 +1,28 @@
+import { EuiComboBox, EuiFormRow } from "@elastic/eui"
+
+export default (props) => {
+ const { indexPattern, spec, onChange } = props;
+ const keys = Object.keys(spec?.function || {})
+ const statistic = keys[0]
+ const func = spec?.function?.[statistic]
+ const { field, group } = func || {}
+
+ return (
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name }
+ ))}
+ selectedOptions={field ? [{ value: field, label: field }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ field: value[0]?.value
+ }})
+ }}
+ isClearable={false}
+ />
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/rate_sum_func_value_in_group.jsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/rate_sum_func_value_in_group.jsx
new file mode 100644
index 00000000..67781352
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/rate_sum_func_value_in_group.jsx
@@ -0,0 +1,73 @@
+import { EuiComboBox, EuiFormRow } from "@elastic/eui"
+import { getStatistics } from ".";
+
+export default (props) => {
+ const { indexPattern, spec, onChange } = props;
+ const keys = Object.keys(spec?.function || {})
+ const statistic = keys[0]
+ const func = spec?.function?.[statistic]
+ const { field, group } = func || {}
+
+ return (
+ <>
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name, type: item.spec?.type }
+ ))}
+ selectedOptions={field ? [{ value: field, label: field }] : []}
+ onChange={(value) => {
+ const types = getStatistics(value[0]?.type)
+ onChange({ [statistic]: {
+ ...(func || {}),
+ field: value[0]?.value,
+ group: {
+ func: types.includes('max') ? 'max' : types[0]
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name }
+ ))}
+ selectedOptions={group?.field ? [{ value: group?.field, label: group?.field }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ group: {
+ ...(func?.group || {}),
+ field: value[0]?.value,
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+
+ {/*
+ (
+ { value: item, label: item.toUpperCase() }
+ ))}
+ selectedOptions={group?.func ? [{ value: group?.func, label: group?.func?.toUpperCase() }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ group: {
+ ...(group || {}),
+ func: value[0]?.value
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+ */}
+ >
+ )
+}
\ No newline at end of file
diff --git a/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/sum_func_value_in_group.jsx b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/sum_func_value_in_group.jsx
new file mode 100644
index 00000000..b287a448
--- /dev/null
+++ b/web/src/components/vendor/index_pattern_management/public/components/field_editor/functions/sum_func_value_in_group.jsx
@@ -0,0 +1,74 @@
+import { EuiComboBox, EuiFormRow } from "@elastic/eui"
+import { useState } from "react";
+import { getStatistics } from ".";
+
+export default (props) => {
+ const { indexPattern, spec, onChange } = props;
+ const keys = Object.keys(spec?.function || {})
+ const statistic = keys[0]
+ const func = spec?.function?.[statistic]
+ const { field, group } = func || {}
+
+ return (
+ <>
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name, type: item.spec?.type }
+ ))}
+ selectedOptions={field ? [{ value: field, label: field }] : []}
+ onChange={(value) => {
+ const types = getStatistics(value[0]?.type)
+ onChange({ [statistic]: {
+ ...(func || {}),
+ field: value[0]?.value,
+ group: {
+ func: types.includes('max') ? 'max' : types[0]
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+
+
+ !!item.spec?.name).map((item) => (
+ { value: item.spec?.name, label: item.spec?.name }
+ ))}
+ selectedOptions={group?.field ? [{ value: group?.field, label: group?.field }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ group: {
+ ...(func?.group || {}),
+ field: value[0]?.value,
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+
+ {/*
+ (
+ { value: item, label: item.toUpperCase() }
+ ))}
+ selectedOptions={group?.func ? [{ value: group?.func, label: group?.func?.toUpperCase() }] : []}
+ onChange={(value) => {
+ onChange({ [statistic]: {
+ ...(func || {}),
+ group: {
+ ...(group || {}),
+ func: value[0]?.value
+ }
+ }})
+ }}
+ isClearable={false}
+ />
+ */}
+ >
+ )
+}
\ No newline at end of file
diff --git a/web/src/locales/en-US.js b/web/src/locales/en-US.js
index 8bde6bbe..a50e799d 100644
--- a/web/src/locales/en-US.js
+++ b/web/src/locales/en-US.js
@@ -81,6 +81,7 @@ export default {
"form.publicUsers.option.B": "Colleague B",
"form.publicUsers.option.C": "Colleague C",
"form.button.search": "Search",
+ "form.button.apply": "Apply",
"form.button.new": "New",
"form.button.create": "Create",
"form.button.add": "Add",
diff --git a/web/src/locales/en-US/cluster.js b/web/src/locales/en-US/cluster.js
index 48c75688..41173891 100644
--- a/web/src/locales/en-US/cluster.js
+++ b/web/src/locales/en-US/cluster.js
@@ -119,6 +119,10 @@ export default {
"cluster.monitor.timepicker.lastyear": "Last year",
"cluster.monitor.timepicker.today": "Today",
+ "cluster.monitor.topn.area": "Area Metric",
+ "cluster.monitor.topn.color": "Color Metric",
+ "cluster.monitor.topn.theme": "Theme",
+
"cluster.metrics.axis.index_throughput.title": "Indexing Rate",
"cluster.metrics.axis.search_throughput.title": "Search Rate",
"cluster.metrics.axis.index_latency.title": "Indexing Latency",
diff --git a/web/src/locales/zh-CN.js b/web/src/locales/zh-CN.js
index 895cb22b..aa22f362 100644
--- a/web/src/locales/zh-CN.js
+++ b/web/src/locales/zh-CN.js
@@ -86,6 +86,7 @@ export default {
"form.logstash.kafkaconf.label": "Logstash Kafka 配置",
"form.logstash.kafkaconf.placeholder": "请输入Kafka配置",
"form.button.search": "搜索",
+ "form.button.apply": "应用",
"form.button.new": "新建",
"form.button.create": "创建",
"form.button.add": "添加",
diff --git a/web/src/locales/zh-CN/cluster.js b/web/src/locales/zh-CN/cluster.js
index ef0dae6d..63865abb 100644
--- a/web/src/locales/zh-CN/cluster.js
+++ b/web/src/locales/zh-CN/cluster.js
@@ -110,6 +110,10 @@ export default {
"cluster.monitor.timepicker.lastyear": "最近1年",
"cluster.monitor.timepicker.today": "今天",
+ "cluster.monitor.topn.area": "面积指标",
+ "cluster.monitor.topn.color": "颜色指标",
+ "cluster.monitor.topn.theme": "主题",
+
"cluster.metrics.axis.index_throughput.title": "索引吞吐",
"cluster.metrics.axis.search_throughput.title": "查询吞吐",
"cluster.metrics.axis.index_latency.title": "索引延迟",
diff --git a/web/src/pages/DataManagement/Discover.jsx b/web/src/pages/DataManagement/Discover.jsx
index 9d1c0d9e..a73cdab4 100644
--- a/web/src/pages/DataManagement/Discover.jsx
+++ b/web/src/pages/DataManagement/Discover.jsx
@@ -112,15 +112,15 @@ const Discover = (props) => {
const [timeZone, setTimeZone] = useState(() => getTimezone());
const [mode, setMode] = useState("table");
- const [viewLayout, setViewLayout] = useState();
+ // const [viewLayout, setViewLayout] = useState();
const insightBarRef = useRef();
const visRef = useRef();
- const layoutRef = useRef();
+ // const layoutRef = useRef();
const rangeCacheRef = useRef();
const fullScreenHandle = useFullScreenHandle();
- const [layout, setLayout] = useState(
- Layouts.find((item) => item.name === "default")
- );
+ // const [layout, setLayout] = useState(
+ // Layouts.find((item) => item.name === "default")
+ // );
const [timeTipsLoading, setTimeTipsLoading] = useState(false);
const [insightLoading, setInsightLoading] = useState(false);
const [showResultCount, setShowResultCount] = useState(true);
@@ -293,9 +293,9 @@ const Discover = (props) => {
getFilters()
);
}
- if (mode === "layout") {
- layoutRef?.current?.onRefresh();
- }
+ // if (mode === "layout") {
+ // layoutRef?.current?.onRefresh();
+ // }
if (!indexPatternRef.current) {
return;
}
@@ -415,7 +415,7 @@ const Discover = (props) => {
columns: record.filter?.columns || ["_source"],
}
if (record.time_field) {
- newState.sort = [{[record.time_field]: {order: "desc"}}]
+ newState.sort = [[record.time_field, 'desc']]
}
setState(newState);
if (record.filter?.filters?.length > 0) {
@@ -881,24 +881,24 @@ const Discover = (props) => {
}
};
- const fetchViewDefaultLayout = async (clusterId, viewId) => {
- setInsightLoading(true);
- const res = await request(
- `/elasticsearch/${clusterId}/saved_objects/view/${viewId}`
- );
- const layoutId = res?._source?.default_layout_id;
- if (layoutId) {
- const layout = await request(`/layout/${layoutId}`);
- if (layout?._source) {
- setViewLayout(layout?._source);
- } else {
- setViewLayout();
- }
- } else {
- setViewLayout();
- }
- setInsightLoading(false);
- };
+ // const fetchViewDefaultLayout = async (clusterId, viewId) => {
+ // setInsightLoading(true);
+ // const res = await request(
+ // `/elasticsearch/${clusterId}/saved_objects/view/${viewId}`
+ // );
+ // const layoutId = res?._source?.default_layout_id;
+ // if (layoutId) {
+ // const layout = await request(`/layout/${layoutId}`);
+ // if (layout?._source) {
+ // setViewLayout(layout?._source);
+ // } else {
+ // setViewLayout();
+ // }
+ // } else {
+ // setViewLayout();
+ // }
+ // setInsightLoading(false);
+ // };
const onFieldAgg = async (field, beforeFuc, afterFuc) => {
let name = field?.spec?.name || field?.name
@@ -993,27 +993,27 @@ const Discover = (props) => {
}
}
- useEffect(() => {
- if (indexPattern?.type === "view") {
- fetchViewDefaultLayout(props.selectedCluster?.id, indexPattern?.id);
- } else {
- setViewLayout();
- }
- }, [indexPattern, props.selectedCluster?.id]);
+ // useEffect(() => {
+ // if (indexPattern?.type === "view") {
+ // fetchViewDefaultLayout(props.selectedCluster?.id, indexPattern?.id);
+ // } else {
+ // setViewLayout();
+ // }
+ // }, [indexPattern, props.selectedCluster?.id]);
- useEffect(() => {
- setMode(viewLayout ? "layout" : "table");
- }, [viewLayout]);
+ // useEffect(() => {
+ // setMode(viewLayout ? "layout" : "table");
+ // }, [viewLayout]);
- useEffect(() => {
- if (mode === "table" && viewLayout) {
- setViewLayout();
- }
- }, [mode]);
+ // useEffect(() => {
+ // if (mode === "table" && viewLayout) {
+ // setViewLayout();
+ // }
+ // }, [mode]);
- const showLayoutListIcon = useMemo(() => {
- return indexPattern?.type === "view";
- }, [indexPattern]);
+ // const showLayoutListIcon = useMemo(() => {
+ // return indexPattern?.type === "view";
+ // }, [indexPattern]);
return (
@@ -1104,10 +1104,10 @@ const Discover = (props) => {
getBucketSize,
columns: state.columns,
}}
- layoutConfig={{
- layout,
- onChange: setLayout,
- }}
+ // layoutConfig={{
+ // layout,
+ // onChange: setLayout,
+ // }}
isEmpty={resultState === "none" && queryFrom === 0}
onQueriesSelect={onQueriesSelect}
onQueriesRemove={(id) => {
@@ -1159,15 +1159,15 @@ const Discover = (props) => {
break;
}
}}
- showLayoutListIcon={showLayoutListIcon}
- viewLayout={viewLayout}
- onViewLayoutChange={(layout) => {
- if (layout) {
- setViewLayout(layout);
- } else {
- setViewLayout();
- }
- }}
+ showLayoutListIcon={false}
+ // viewLayout={viewLayout}
+ // onViewLayoutChange={(layout) => {
+ // if (layout) {
+ // setViewLayout(layout);
+ // } else {
+ // setViewLayout();
+ // }
+ // }}
/>
@@ -1200,20 +1200,22 @@ const Discover = (props) => {
getFilters={getSearchFilters}
getBucketSize={getBucketSize}
fullScreenHandle={fullScreenHandle}
- layout={layout}
+ // layout={layout}
selectedQueries={selectedQueries}
/>
- ) : mode === "layout" ? (
-
- ) : (
+ ) :
+ // mode === "layout" ? (
+ //
+ // ) :
+ (
<>
{indexPattern && (
diff --git a/web/src/pages/DataManagement/IndexPatterns.jsx b/web/src/pages/DataManagement/IndexPatterns.jsx
index 58ed3a2e..bd16c017 100644
--- a/web/src/pages/DataManagement/IndexPatterns.jsx
+++ b/web/src/pages/DataManagement/IndexPatterns.jsx
@@ -19,6 +19,7 @@ import { formatMessage } from "umi/locale";
import { getAuthority, hasAuthority } from "@/utils/authority";
import EditLayout from "./View/EditLayout";
import { Card, Empty } from "antd";
+import { CreateEditComplexFieldContainer } from "@/components/vendor/index_pattern_management/public/components/edit_index_pattern/create_edit_complex_field";
const IndexPatterns = (props) => {
if (!props.selectedCluster?.id) {
@@ -57,15 +58,11 @@ const IndexPatterns = (props) => {
-
+
{
>
- {
- !isAgent && (
-
-
-
- )
- }
+
+
+
{
isAgent && (
{
+ function colorToRgb(color) {
+ const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
+ return rgb ? {
+ r: parseInt(rgb[1], 16),
+ g: parseInt(rgb[2], 16),
+ b: parseInt(rgb[3], 16)
+ } : null;
+ }
+
+ function rgbToHex(r, g, b) {
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
+ }
+
+ const startRGB = colorToRgb(startColor);
+ const endRGB = colorToRgb(endColor);
+ const diffR = endRGB.r - startRGB.r;
+ const diffG = endRGB.g - startRGB.g;
+ const diffB = endRGB.b - startRGB.b;
+
+ const colors = [];
+ for (let i = 0; i <= steps; i++) {
+ const r = startRGB.r + (diffR * i / steps);
+ const g = startRGB.g + (diffG * i / steps);
+ const b = startRGB.b + (diffB * i / steps);
+ colors.push(rgbToHex(Math.round(r), Math.round(g), Math.round(b)));
+ }
+
+ return colors;
+}
+
+export const generateColors = (colors, data) => {
+ if (!colors || colors.length <= 1 || !data || data.length <= 1 || data.length <= colors.length) return colors
+ const gradientSize = data.length - colors.length
+ const steps = Math.floor(gradientSize / (colors.length - 1)) + 1
+ let remainder = gradientSize % (colors.length - 1)
+ const newColors = []
+ for(let i=0; i 0) {
+ fixSteps++
+ remainder--
+ }
+ const gradientColors = generateGradientColors(colors[i], colors[i+1], fixSteps)
+ newColors.push(...gradientColors.slice(0, gradientColors.length - 1))
+ }
+ newColors.push(colors[colors.length - 1])
+ return newColors
+}
+
+export const fixFormatter = (formatType, pattern = '0,0.[00]a') => {
+ return getFormatter(formatType === 'number' ? 'num' : formatType, formatType === 'number' ? pattern : '')
+}
+
+export const handleTextOverflow = (text, maxWidth) => {
+ if (!text || text.length <= 3) return text
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ let textWidth = ctx.measureText(text).width;
+ if (textWidth > maxWidth) {
+ let size = text.length - 3;
+ let newText = text.substr(0, size) + '...'
+ while (textWidth > maxWidth) {
+ size--
+ newText = text.substr(0, size) + '...'
+ textWidth = ctx.measureText(newText).width;
+ }
+ return text.substr(0, size - 5) + '...'
+ } else {
+ return text
+ }
+}
+
+export default (props) => {
+
+ const { config = {}, data = [] } = props
+
+ return (
+
+ { data.length === 0 || data.some((item) => !Number.isFinite(item.value)) ? (
+
+
+
+ ) : (
+ config?.sourceArea?.name ? (
+
+ ) : (
+
+ )
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/Heatmap.jsx b/web/src/pages/Platform/Overview/components/TopN/Heatmap.jsx
new file mode 100644
index 00000000..6f2155ae
--- /dev/null
+++ b/web/src/pages/Platform/Overview/components/TopN/Heatmap.jsx
@@ -0,0 +1,213 @@
+import { Heatmap } from "@ant-design/charts";
+import { cloneDeep } from "lodash";
+import { useEffect, useMemo, useRef, useState } from "react";
+import * as uuid from 'uuid';
+import { fixFormatter, generateColors, handleTextOverflow } from "./Chart";
+
+function findMaxSize(a, b, n) {
+ let c = Math.min(a, b);
+ let rows, cols;
+
+ while (c >= 50) {
+ cols = Math.floor(a / c);
+ rows = Math.floor(b / c);
+ if (cols * rows >= n) {
+ return { rows: rows, cols: cols, itemWidth: c };
+ }
+ c-=0.01;
+ }
+ cols = Math.floor(a / 50)
+ rows = Math.ceil(n / cols)
+ return { rows, cols, itemWidth: c };
+}
+
+export default (props) => {
+ const { config = {}, data = [] } = props
+ const {
+ top,
+ colors = [],
+ sourceColor = {},
+ } = config;
+
+ const containerRef = useRef()
+ const [size, setSize] = useState()
+
+ const color = useMemo(() => {
+ if (colors.length === 0 || !sourceColor?.key || data.length === 0) return undefined
+ const newColors = generateColors(colors, data).reverse()
+ if (newColors.length === 0) return undefined
+ return (a, b, c) => {
+ if (Number.isFinite(a?.value)) {
+ const splits = `${a.value}`.split('2024')
+ const value = Number(splits[1])
+ const index = Number(splits[0])
+ return newColors[index] || (value == 0 ? newColors[newColors.length - 1] : newColors[0])
+ } else {
+ return '#fff'
+ }
+ }
+ }, [data, colors, sourceColor])
+
+ const formatData = useMemo(() => {
+ if (!data || data.length === 0 || !size?.cols) return [];
+ const cols = size?.cols
+ const newData = []
+ let rowData = []
+ data.forEach((item, index) => {
+ if (rowData.length === cols) {
+ newData.unshift(cloneDeep(rowData))
+ rowData = []
+ }
+ rowData.push({
+ item,
+ name: item.name,
+ col: `${index % cols}`,
+ row: `${Math.floor(index / cols)}`,
+ value: Number(`${index}2024${item.valueColor}`)
+ })
+ })
+ if (rowData.length !== cols) {
+ const size = cols - rowData.length
+ for (let i=data.length; i<(data.length + size); i++) {
+ rowData.push({
+ col: `${i % cols}`,
+ row: `${Math.floor(i / cols)}`,
+ value: null
+ })
+ }
+ }
+ newData.unshift(rowData)
+ return [].concat.apply([], newData)
+ }, [JSON.stringify(data), size?.cols])
+
+ const handleResize = (size) => {
+ if (containerRef.current) {
+ const { offsetWidth, offsetHeight } = containerRef.current
+ if (size === 1) {
+ setSize({
+ rows: 1,
+ cols: 1,
+ itemWidth: offsetWidth,
+ width: '100%',
+ height: '100%'
+ })
+ } else {
+ let { cols, rows, itemWidth } = findMaxSize(offsetWidth, offsetHeight, size)
+ if (cols * itemWidth < offsetWidth) {
+ cols++
+ itemWidth = offsetWidth / cols
+ rows = Math.ceil(size / cols)
+ }
+
+ setSize({
+ rows,
+ cols,
+ itemWidth,
+ width: cols * itemWidth,
+ height: rows * itemWidth
+ })
+ }
+ }
+ }
+
+ useEffect(() => {
+ handleResize(data.length)
+ const onResize = () => {
+ handleResize(data.length)
+ }
+ window.addEventListener('resize', onResize)
+ return () => {
+ window.removeEventListener('resize', onResize)
+ }
+ }, [JSON.stringify(data)])
+
+ return (
+
+
+
{
+ return handleTextOverflow(a.name, size?.itemWidth)
+ }
+ },
+ heatmapStyle: {
+ lineWidth: 0
+ },
+ xAxis: {
+ tickLine: null,
+ label: null,
+ },
+ yAxis: {
+ tickLine: null,
+ label: null,
+ },
+ legend: false,
+ tooltip: {
+ customContent: (title, items) => {
+ if (!items[0]) return;
+ const { color, data } = items[0];
+ const { item } = data;
+
+ if (item) {
+ const { format: formatColor, pattern: patternColor, unit: unitColor } = sourceColor || {}
+ const { name, value, nameColor, valueColor, displayName } = item || {}
+ const formatterColor = fixFormatter(formatColor, patternColor)
+ const markers = []
+ markers.push({
+ name: nameColor,
+ value: formatterColor ? formatterColor(valueColor) : valueColor,
+ unit: unitColor,
+ marker:
+ })
+ return (
+
+ {
+
+ {displayName}
+
+ }
+
+ {
+ markers.map((item, index) => (
+
+ {item.marker}
+
+ {item.name}:
+
+ {item.unit ? `${item.value}${item.unit}` : item.value}
+
+
+
+ ))
+ }
+
+
+ );
+ } else {
+ return null
+ }
+ },
+ }
+ }} />
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/Table.jsx b/web/src/pages/Platform/Overview/components/TopN/Table.jsx
index 62ebf88e..838c28ed 100644
--- a/web/src/pages/Platform/Overview/components/TopN/Table.jsx
+++ b/web/src/pages/Platform/Overview/components/TopN/Table.jsx
@@ -3,7 +3,7 @@ import { Treemap } from "@ant-design/charts";
import { Table } from "antd";
import { useMemo } from "react";
import { formatMessage } from "umi/locale";
-import { fixFormatter } from "./Treemap";
+import { fixFormatter } from "./Chart";
export default (props) => {
@@ -23,8 +23,8 @@ export default (props) => {
key: 'displayName',
}];
if (sourceArea) {
- const { format: formatArea, unit: unitArea } = sourceArea || {}
- const formatterArea = fixFormatter(formatArea)
+ const { format: formatArea, pattern: patternArea, unit: unitArea } = sourceArea || {}
+ const formatterArea = fixFormatter(formatArea, patternArea)
newColumns.push({
title: unitArea ? `${sourceArea.name}(${unitArea})` : sourceArea.name,
dataIndex: 'value',
@@ -35,8 +35,8 @@ export default (props) => {
})
}
if (sourceColor) {
- const { format: formatColor, unit: unitColor } = sourceColor
- const formatterColor = fixFormatter(formatColor)
+ const { format: formatColor, pattern: patternColor, unit: unitColor } = sourceColor
+ const formatterColor = fixFormatter(formatColor, patternColor)
newColumns.push({
title: unitColor ? `${sourceColor.name}(${unitColor})` : sourceColor.name,
dataIndex: 'valueColor',
diff --git a/web/src/pages/Platform/Overview/components/TopN/Treemap.jsx b/web/src/pages/Platform/Overview/components/TopN/Treemap.jsx
index afe69792..ee52f766 100644
--- a/web/src/pages/Platform/Overview/components/TopN/Treemap.jsx
+++ b/web/src/pages/Platform/Overview/components/TopN/Treemap.jsx
@@ -1,61 +1,7 @@
import { getFormatter } from "@/utils/format";
import { Treemap } from "@ant-design/charts";
-import { Empty } from "antd";
import { useMemo } from "react";
-
-const generateGradientColors = (startColor, endColor, steps) => {
- function colorToRgb(color) {
- const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
- return rgb ? {
- r: parseInt(rgb[1], 16),
- g: parseInt(rgb[2], 16),
- b: parseInt(rgb[3], 16)
- } : null;
- }
-
- function rgbToHex(r, g, b) {
- return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
- }
-
- const startRGB = colorToRgb(startColor);
- const endRGB = colorToRgb(endColor);
- const diffR = endRGB.r - startRGB.r;
- const diffG = endRGB.g - startRGB.g;
- const diffB = endRGB.b - startRGB.b;
-
- const colors = [];
- for (let i = 0; i <= steps; i++) {
- const r = startRGB.r + (diffR * i / steps);
- const g = startRGB.g + (diffG * i / steps);
- const b = startRGB.b + (diffB * i / steps);
- colors.push(rgbToHex(Math.round(r), Math.round(g), Math.round(b)));
- }
-
- return colors;
-}
-
-const generateColors = (colors, data) => {
- if (!colors || colors.length <= 1 || !data || data.length <= 1 || data.length <= colors.length) return colors
- const gradientSize = data.length - colors.length
- const steps = Math.floor(gradientSize / (colors.length - 1)) + 1
- let remainder = gradientSize % (colors.length - 1)
- const newColors = []
- for(let i=0; i 0) {
- fixSteps++
- remainder--
- }
- const gradientColors = generateGradientColors(colors[i], colors[i+1], fixSteps)
- newColors.push(...gradientColors.slice(0, gradientColors.length - 1))
- }
- newColors.push(colors[colors.length - 1])
- return newColors
-}
-
-export const fixFormatter = (formatType) => {
- return getFormatter(formatType === 'number' ? 'num' : formatType, formatType === 'number' ? '0,0.[00]a' : '')
-}
+import { fixFormatter, generateColors } from "./Chart";
export default (props) => {
@@ -69,12 +15,12 @@ export default (props) => {
const color = useMemo(() => {
if (colors.length === 0 || !sourceColor?.key || data.length === 0) return undefined
- const newColors = generateColors(colors, data)
- const sortData = data.sort((a, b) => a.valueColor - b.valueColor)
+ const newColors = generateColors(colors, data).reverse()
+ if (newColors.length === 0) return undefined
return ({ name }) => {
- const index = sortData.findIndex((item) => item.name === name)
+ const index = data.findIndex((item) => item.name === name)
if (index !== -1) {
- return newColors[index] || newColors[0]
+ return newColors[index] || (value == 0 ? newColors[newColors.length - 1] : newColors[0])
} else {
return newColors[0]
}
@@ -83,12 +29,8 @@ export default (props) => {
return (
- { data.length === 0 || data.some((item) => !Number.isFinite(item.value)) ? (
-
-
-
- ) : (
-
{
autoFit: true,
color,
colorField: 'name',
- legend: {
- position: 'top-left',
- itemName: {
- formatter: (text) => {
- const item = data.find((item) => item.name === text)
- return item?.groupName || text
- }
- }
- },
label: {
- formatter: (item) => item.displayName
+ formatter: (item, a, b, c) => item.displayName
+ },
+ legend: {
+ position: 'top-left',
+ itemName: {
+ formatter: (text) => {
+ const item = data.find((item) => item.name === text)
+ return item?.groupName || text
+ }
+ }
},
tooltip: {
customContent: (title, items) => {
@@ -117,8 +59,8 @@ export default (props) => {
const markers = []
if (metricArea && tooltipArea !== false) {
- const { format: formatArea, unit: unitArea } = sourceArea || {}
- const formatterArea = fixFormatter(formatArea)
+ const { format: formatArea, pattern: patternArea, unit: unitArea } = sourceArea || {}
+ const formatterArea = fixFormatter(formatArea, patternArea)
markers.push({
name: nameArea,
value: formatterArea ? formatterArea(value) : value,
@@ -128,8 +70,8 @@ export default (props) => {
}
if (metricColor) {
- const { format: formatColor, unit: unitColor } = sourceColor || {}
- const formatterColor = fixFormatter(formatColor)
+ const { format: formatColor, pattern: patternColor, unit: unitColor } = sourceColor || {}
+ const formatterColor = fixFormatter(formatColor, patternColor)
markers.push({
name: nameColor,
value: formatterColor ? formatterColor(valueColor) : valueColor,
@@ -170,7 +112,6 @@ export default (props) => {
},
}
}} />
- )}
)
}
\ No newline at end of file
diff --git a/web/src/pages/Platform/Overview/components/TopN/index.jsx b/web/src/pages/Platform/Overview/components/TopN/index.jsx
index 311986a6..f6ce7c5c 100644
--- a/web/src/pages/Platform/Overview/components/TopN/index.jsx
+++ b/web/src/pages/Platform/Overview/components/TopN/index.jsx
@@ -6,63 +6,146 @@ import { formatMessage } from "umi/locale";
import ConvertSvg from "@/components/Icons/Convert"
import { useEffect, useMemo, useRef, useState } from "react";
import ColorPicker from "./ColorPicker";
-import Treemap from "./Treemap";
+import Chart from "./Chart";
import Table from "./Table";
import GradientColorPicker from "./GradientColorPicker";
import { cloneDeep } from "lodash";
import request from "@/utils/request";
import { formatTimeRange } from "@/lib/elasticsearch/util";
import { CopyToClipboard } from "react-copy-to-clipboard";
-import * as uuid from 'uuid';
+import { getRollupEnabled } from "@/utils/authority";
+import { getStatistics, ROLLUP_FIELDS } from "@/components/vendor/index_pattern_management/public/components/field_editor/field_editor";
+
+const DEFAULT_TOP = 15;
+const DEFAULT_COLORS = ['#00bb1b', '#fcca00', '#ff4d4f']
+
+function generate20BitUUID() {
+ let characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ let uuid = characters[Math.floor(Math.random() * characters.length)];
+ const buffer = new Uint8Array(9);
+ crypto.getRandomValues(buffer);
+ for (let i = 0; i < buffer.length; i++) {
+ uuid += buffer[i].toString(16).padStart(2, '0');
+ }
+ return uuid.slice(0, 20);
+}
+
+const formatComplexStatistic = (statistic) => {
+ if (statistic?.includes('rate')) {
+ return 'rate'
+ } else if (statistic?.includes('latency')) {
+ return 'latency'
+ }
+ return 'max'
+}
export default (props) => {
- const { type, clusterID, timeRange } = props;
+ const { type, clusterID, timeRange, isAgent } = props;
const [currentMode, setCurrentMode] = useState('treemap')
- const [metrics, setMetrics] = useState([])
-
const [formData, setFormData] = useState({
- top: 15,
- colors: ['#00bb1b', '#fcca00', '#ff4d4f']
+ top: DEFAULT_TOP,
+ colors: DEFAULT_COLORS
})
+ const isRollupEnabled = getRollupEnabled() === 'true'
+
const [config, setConfig] = useState({})
const [loading, setLoading] = useState(false)
const [data, setData] = useState([])
const [result, setResult] = useState()
+ const [selectedView, setSelectedView] = useState()
const searchParamsRef = useRef()
- const fetchMetrics = async (type) => {
+ const fetchFields = async (clusterID, viewID, type, isAgent) => {
+ if (!clusterID || !viewID) return;
setLoading(true)
- const res = await request(`/collection/metric/_search`, {
+ const res = await request(`/elasticsearch/${clusterID}/saved_objects/_bulk_get`, {
method: 'POST',
- body: {
- size: 10000,
- from: 0,
- query: { bool: { filter: [{ "term": { "level": type === 'index' ? 'indices' : type } }] }}
- }
+ body: [{
+ id: viewID,
+ type: "view"
+ }]
})
- if (res?.hits?.hits) {
- const newMetrics = res?.hits?.hits.filter((item) => {
- const { items = [] } = item._source;
- if (items.length === 0) return false
- return true;
- }).map((item) => ({ ...item._source }))
- setMetrics(newMetrics)
- if (newMetrics.length > 0 && (!formData.sourceArea && !formData.sourceColor)) {
+ if (res && !res.error && Array.isArray(res.saved_objects) && res.saved_objects[0]) {
+ const newView = res.saved_objects[0]
+ let { fieldFormatMap, fields } = newView.attributes || {}
+ let { complex_fields: complexFields } = newView
+ try {
+ fieldFormatMap = JSON.parse(fieldFormatMap) || {}
+ fields = JSON.parse(fields) || []
+ complexFields = JSON.parse(complexFields)
+ const keys = Object.keys(complexFields || {})
+ complexFields = keys.map((key) => {
+ const item = complexFields?.[key] || {}
+ return {
+ name: key,
+ metric_config: item
+ }
+ })
+ } catch (error) {
+ fieldFormatMap = {}
+ fields = []
+ }
+ if (!Array.isArray(fields)) fields = []
+ if (!Array.isArray(complexFields)) complexFields = []
+ if (!newView.attributes) newView.attributes = {}
+ newView.fieldFormatMap = fieldFormatMap
+ newView.fields = fields.filter((item) =>
+ !!item.metric_config &&
+ !!item.metric_config.name &&
+ item.metric_config.tags?.includes(type === 'index' ? 'indices' : type) &&
+ item.metric_config.tags?.includes(isAgent ? 'agent' : 'agentless')
+ ).map((item) => {
+ if(!item.metric_config.option_aggs || item.metric_config.option_aggs.length === 0) {
+ item.metric_config.option_aggs = isRollupEnabled ? ROLLUP_FIELDS[item.name]: getStatistics(item.type)
+ }
+ return {
+ ...item,
+ format: fieldFormatMap[item.name]?.id || 'number',
+ pattern: fieldFormatMap[item.name]?.params?.pattern,
+ }
+ }).concat(complexFields.filter((item) =>
+ !!item.metric_config &&
+ !!item.metric_config.name &&
+ !!item.metric_config.function &&
+ item.metric_config.tags?.includes(type === 'index' ? 'indices' : type) &&
+ item.metric_config.tags?.includes(isAgent ? 'agent' : 'agentless')
+ ).map((item) => {
+ item.metric_config.option_aggs = [formatComplexStatistic(Object.keys(item.metric_config.function || {})[0])]
+ return {
+ ...item,
+ format: fieldFormatMap[item.name]?.id || 'number',
+ pattern: fieldFormatMap[item.name]?.params?.pattern,
+ isComplex: true,
+ }
+ })).filter((item) =>
+ item.metric_config.option_aggs &&
+ item.metric_config.option_aggs.length > 0
+ )
+
+ setSelectedView(newView)
+ if (newView.fields.length > 0 && (!formData.sourceArea && !formData.sourceColor)) {
+ const initField = newView.fields[0]
+ const initStatistic = newView.fields[0].metric_config?.option_aggs?.[0] || 'max'
const newFormData = {
...cloneDeep(formData),
- sourceArea: newMetrics[0],
- statisticArea: newMetrics[0]?.items[0]?.statistic,
- sourceColor: newMetrics[0],
- statisticColor: newMetrics[0].items[0]?.statistic
+ sourceArea: initField,
+ statisticArea: initStatistic,
+ sourceColor: initField,
+ statisticColor: initStatistic
}
setFormData(newFormData)
fetchData(type, clusterID, timeRange, newFormData)
}
+ } else {
+ setFormData({
+ top: DEFAULT_TOP,
+ colors: DEFAULT_COLORS
+ })
}
setLoading(false)
}
@@ -72,20 +155,94 @@ export default (props) => {
if (shouldLoading) {
setLoading(true)
}
- const { top, sourceArea = {}, statisticArea, statisticColor, sourceColor = {} } = formData
+ const { top, sourceArea, statisticArea, statisticColor, sourceColor } = formData
const newTimeRange = formatTimeRange(timeRange);
searchParamsRef.current = { type, clusterID, formData }
- const sortKey = sourceArea?.items?.[0]?.name || sourceColor?.items?.[0]?.name
+ let areaValueID
+ let colorValueID
+ let areaFormula
+ let colorFormula
+ const items = []
+ const formulas = []
+ let isAreaRate = false
+ let isColorRate = false
+ let isAreaLatency = false
+ let isColorLatency = false
+ if (sourceArea) {
+ areaValueID = generate20BitUUID()
+ if (sourceArea.isComplex) {
+ if (sourceArea.metric_config.function) {
+ const func = Object.keys(sourceArea.metric_config.function || {})?.[0]
+ if (func?.includes('rate')) {
+ isAreaRate = true
+ }
+ if (func?.includes('latency')) {
+ isAreaLatency = true
+ }
+ items.push({
+ name: areaValueID,
+ function: sourceArea.metric_config.function,
+ })
+ areaFormula = isAreaRate ? `${areaValueID}/{{.bucket_size_in_second}}` : areaValueID
+ formulas.push(areaFormula)
+ }
+ } else {
+ if (statisticArea) {
+ items.push({
+ function: {
+ [statisticArea]: {
+ field: sourceArea.name,
+ }
+ },
+ name: areaValueID,
+ })
+ areaFormula = areaValueID
+ formulas.push(areaFormula)
+ }
+ }
+ }
+ if (sourceColor) {
+ colorValueID = generate20BitUUID()
+ if (sourceColor.isComplex) {
+ if (sourceColor.metric_config.function) {
+ const func = Object.keys(sourceColor.metric_config.function || {})?.[0]
+ if (func?.includes('rate')) {
+ isColorRate = true
+ }
+ if (func?.includes('latency')) {
+ isColorLatency = true
+ }
+ items.push({
+ name: colorValueID,
+ function: sourceColor.metric_config.function,
+ })
+ colorFormula = isColorRate ? `${colorValueID}/{{.bucket_size_in_second}}` : colorValueID
+ formulas.push(colorFormula)
+ }
+ } else {
+ if (statisticColor) {
+ items.push({
+ function: {
+ [statisticColor]: {
+ field: sourceColor.name,
+ }
+ },
+ name: colorValueID,
+ })
+ colorFormula = colorValueID
+ formulas.push(colorFormula)
+ }
+ }
+ }
+ const sortKey = areaValueID || colorValueID
const body = {
"index_pattern": ".infini_metrics*",
- "time_field": "timestamp",
- "bucket_size": "auto",
"filter": {
"bool": {
"must": [{
"term": {
"metadata.name": {
- "value": `${type}_stats`
+ "value": isAgent && type === 'index' ? `shard_stats` : `${type}_stats`
}
}
}, {
@@ -116,20 +273,8 @@ export default (props) => {
],
}
},
- "formulas": [sourceArea?.formula, sourceColor?.formula].filter((item) => !!item),
- "items": [...(sourceArea?.items || [])?.map((item) => {
- item.statistic = statisticArea
- if (item.statistic === 'rate') {
- item.statistic = 'derivative'
- }
- return item
- }),...(sourceColor?.items || [])?.map((item) => {
- item.statistic = statisticColor
- if (item.statistic === 'rate') {
- item.statistic = 'derivative'
- }
- return item
- })].filter((item) => !!item),
+ "formulas": formulas,
+ "items": items,
"groups": [{
"field": type === 'shard' ? `metadata.labels.shard_id` : `metadata.labels.${type}_name`,
"limit": top
@@ -139,9 +284,9 @@ export default (props) => {
"key": sortKey
}] : undefined
}
- if (statisticArea !== 'rate' && statisticColor !== 'rate') {
- delete body['time_field']
- delete body['bucket_size']
+ if ((isAreaRate || isColorRate) || (isAreaLatency || isColorLatency)) {
+ body['time_field'] = "timestamp"
+ body['bucket_size'] = "auto"
}
const res = await request(`/elasticsearch/infini_default_system_cluster/visualization/data`, {
method: 'POST',
@@ -149,7 +294,28 @@ export default (props) => {
})
if (res && !res.error) {
setResult(res)
- setConfig(cloneDeep(formData))
+ const newConfig = cloneDeep(formData)
+ if (newConfig.sourceArea?.name && newConfig.sourceArea?.metric_config?.name) {
+ newConfig.sourceArea = {
+ key: newConfig.sourceArea?.name,
+ name: newConfig.sourceArea.metric_config.name,
+ formula: areaFormula,
+ format: newConfig.sourceArea.format,
+ pattern: newConfig.sourceArea.pattern,
+ unit: newConfig.sourceArea.metric_config.unit
+ }
+ }
+ if (newConfig.sourceColor?.name && newConfig.sourceColor?.metric_config?.name) {
+ newConfig.sourceColor = {
+ key: newConfig.sourceColor?.name,
+ name: newConfig.sourceColor.metric_config.name,
+ formula: colorFormula,
+ format: newConfig.sourceColor.format,
+ pattern: newConfig.sourceColor.pattern,
+ unit: newConfig.sourceColor.metric_config.unit
+ }
+ }
+ setConfig(newConfig)
} else {
setResult()
}
@@ -179,7 +345,10 @@ export default (props) => {
}
useEffect(() => {
- fetchMetrics(type)
+ fetchFields(clusterID, 'infini_metrics', type, isAgent)
+ }, [clusterID, type, isAgent])
+
+ useEffect(() => {
}, [type])
const isTreemap = useMemo(() => {
@@ -214,7 +383,7 @@ export default (props) => {
sortKey = 'value'
} else {
if (sourceColor) {
- const key = uuid.v4();
+ const key = generate20BitUUID();
object['metricArea'] = `metric_${key}`
object['value'] = 1
object['nameArea'] = `name_${key}`
@@ -270,12 +439,9 @@ export default (props) => {
/>
-
+
+ Top
+
{
precision={0}
onChange={(value) => onFormDataChange({ top: value })}
/>
-
+
+ {formatMessage({ id: "cluster.monitor.topn.area" })}
+
-
-
+
+ {formatMessage({ id: "cluster.monitor.topn.color" })}
+
-
+
+ {formatMessage({ id: "cluster.monitor.topn.theme" })}
+
{
onFormDataChange({ colors: value })
setConfig({
@@ -398,7 +574,7 @@ export default (props) => {
colors: value
})
}}/>
-
+
@@ -414,7 +590,7 @@ export default (props) => {
)
}
- { isTreemap ?
:
}
+ { isTreemap ? : }
diff --git a/web/src/pages/Platform/Overview/components/TopN/index.less b/web/src/pages/Platform/Overview/components/TopN/index.less
index f00b1aa7..3ea68b6c 100644
--- a/web/src/pages/Platform/Overview/components/TopN/index.less
+++ b/web/src/pages/Platform/Overview/components/TopN/index.less
@@ -21,6 +21,20 @@
}
}
}
+
+ .label {
+ width: auto;
+ height: 32px;
+ padding: 0px 8px;
+ font-size: 14px;
+ line-height: 32px;
+ color: rgba(0, 0, 0, 0.25);
+ background-color: #f5f5f5;
+ border: 1px solid #d9d9d9;
+ border-radius: 4px;
+ border-top-right-radius: 0px !important;
+ border-bottom-right-radius: 0px !important;
+ }
}
.content {
diff --git a/web/src/utils/authority.js b/web/src/utils/authority.js
index 29dfe49c..73f07abd 100644
--- a/web/src/utils/authority.js
+++ b/web/src/utils/authority.js
@@ -72,9 +72,14 @@ export function getAuthorizationHeader() {
return "";
}
+export function getRollupEnabled() {
+ return localStorage.getItem("infini-rollup-enabled");
+}
+
(async function() {
- const authRes = await request("/setting/application");
- if (authRes && !authRes.error) {
- localStorage.setItem("infini-auth", authRes.auth_enabled);
+ const res = await request("/setting/application");
+ if (res && !res.error) {
+ localStorage.setItem("infini-auth", res.auth_enabled);
+ localStorage.setItem('infini-rollup-enabled', res.system_cluster?.rollup_enabled || false)
}
})();
diff --git a/web/src/utils/utils.js b/web/src/utils/utils.js
index ed0ec208..e7cf8dbd 100644
--- a/web/src/utils/utils.js
+++ b/web/src/utils/utils.js
@@ -427,3 +427,14 @@ export const formatToUniversalTime = (time, format, timezone) => {
if (!time) return '-';
return moment(time).tz(timezone || getTimezone()).format(format || "YYYY-MM-DD HH:mm:ss (G[M]TZ)")
}
+
+export const generate20BitUUID = () => {
+ let characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ let uuid = characters[Math.floor(Math.random() * characters.length)];
+ const buffer = new Uint8Array(9);
+ crypto.getRandomValues(buffer);
+ for (let i = 0; i < buffer.length; i++) {
+ uuid += buffer[i].toString(16).padStart(2, '0');
+ }
+ return uuid.slice(0, 20);
+}