diff --git a/api/index_management/elasticsearch.go b/api/index_management/elasticsearch.go index 0af6a6b5..84dbcf3a 100644 --- a/api/index_management/elasticsearch.go +++ b/api/index_management/elasticsearch.go @@ -5,7 +5,7 @@ import ( log "github.com/cihub/seelog" httprouter "infini.sh/framework/core/api/router" "infini.sh/framework/core/elastic" - "infini.sh/framework/core/metrics" + "infini.sh/framework/core/event" "infini.sh/framework/core/orm" "infini.sh/framework/core/util" "net/http" @@ -77,7 +77,7 @@ func (handler APIHandler) getLatestClusterMonitorData(clusterID interface{}) (ut ] }` queryDSL := fmt.Sprintf(queryDSLTpl, clusterID) - searchRes, err := client.SearchWithRawQueryDSL(orm.GetIndexName(metrics.MetricEvent{}), []byte(queryDSL)) + searchRes, err := client.SearchWithRawQueryDSL(orm.GetIndexName(event.Event{}), []byte(queryDSL)) if err != nil { return nil, err } diff --git a/main.go b/main.go index 93e8386a..8357413e 100644 --- a/main.go +++ b/main.go @@ -87,7 +87,7 @@ func main() { }, func() { orm.RegisterSchemaWithIndexName(model.Dict{}, "dict") orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex") - orm.RegisterSchemaWithIndexName(elastic.IndexPattern{}, "view") + orm.RegisterSchemaWithIndexName(elastic.View{}, "view") orm.RegisterSchemaWithIndexName(alerting.Config{}, "alerting-config") orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts") orm.RegisterSchemaWithIndexName(alerting.AlertingHistory{}, "alerting-alert-history") diff --git a/web/config/router.config.js b/web/config/router.config.js index bfcdee98..0ade204b 100644 --- a/web/config/router.config.js +++ b/web/config/router.config.js @@ -33,19 +33,10 @@ export default [ // { path: '/', redirect: '/' }, // ], // }, - { - path: '/cluster/overview_new', - name: 'overview', - component: './Cluster/NewOverview', - // hideInMenu: true, - routes:[ - { path: '/', redirect: '/' }, - ], - }, { path: '/cluster/overview', name: 'overview', - component: './Cluster/Overview', + component: './Cluster/NewOverview', // hideInMenu: true, routes:[ { path: '/', redirect: '/' }, diff --git a/web/src/assets/time_field_exmaple.jpg b/web/src/assets/time_field_exmaple.jpg new file mode 100644 index 00000000..dba48c5b Binary files /dev/null and b/web/src/assets/time_field_exmaple.jpg differ diff --git a/web/src/assets/window-maximize.svg b/web/src/assets/window-maximize.svg new file mode 100644 index 00000000..80a32ed5 --- /dev/null +++ b/web/src/assets/window-maximize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/window-restore.svg b/web/src/assets/window-restore.svg new file mode 100644 index 00000000..10febfed --- /dev/null +++ b/web/src/assets/window-restore.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/src/components/infini/resize_bar.scss b/web/src/components/infini/resize_bar.scss index af15713f..bd5b6dcf 100644 --- a/web/src/components/infini/resize_bar.scss +++ b/web/src/components/infini/resize_bar.scss @@ -2,6 +2,7 @@ display: flex; height: 10px; background: #eee; + margin-top: -5px; // border-top: 1px solid #bbb; align-items: center; justify-content: center; diff --git a/web/src/components/infini/tabs/style/flex-bar.scss b/web/src/components/infini/tabs/style/flex-bar.scss index 91071d33..8295961a 100644 --- a/web/src/components/infini/tabs/style/flex-bar.scss +++ b/web/src/components/infini/tabs/style/flex-bar.scss @@ -1,24 +1,55 @@ -.ant-tabs-top-bar{ +.ant-tabs-top-bar { display: flex; } -.ant-tabs-extra-left{ +.ant-tabs-extra-left { order: 1; flex: 0 0 auto; } -.ant-tabs-nav-container{ +.ant-tabs-nav-container { order: 2; flex: 0 0 auto; - } -.flex-tabbar .ant-tabs-top-bar .ant-tabs-nav-container{ - max-width: calc(100% - 140px); +.flex-tabbar { + .ant-tabs-top-bar .ant-tabs-nav-container { + max-width: calc(100% - 140px); + } + .tabbar-icon { + padding: 0 10px; + cursor: pointer; + } + .tabbar-icon:hover { + background-color: #efefef; + } } -.ant-tabs-extra-append{ +.ant-tabs-extra-append { order: 3; - margin-left: 10px; } -.ant-tabs-extra-right{ +.ant-tabs-extra-right { order: 4; margin-left: auto; flex: 0 0 auto; -} \ No newline at end of file +} + +#console { + .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active { + font-weight: 400; + background-color: #939ea0; + color: #fff; + border-color: #939ea0; + } + + .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab { + border-radius: 0; + height: 32px; + } + .ant-tabs.ant-tabs-card .ant-tabs-extra-content { + line-height: 30px; + display: flex; + } + .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-nav-container { + height: 30px; + } + .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab .ant-tabs-close-x { + margin-bottom: 18px; + } +} diff --git a/web/src/components/kibana/console/components/Console.tsx b/web/src/components/kibana/console/components/Console.tsx index 489c09d5..b7853c03 100644 --- a/web/src/components/kibana/console/components/Console.tsx +++ b/web/src/components/kibana/console/components/Console.tsx @@ -55,9 +55,9 @@ const ConsoleWrapper = ({
- - -
+ + +
- + -
+
@@ -101,7 +101,7 @@ const ConsoleWrapper = ({ -
+
void, paneKey: string, + height?: string, } const DEFAULT_INPUT_VALUE = `GET _search @@ -78,7 +79,7 @@ const DEFAULT_INPUT_VALUE = `GET _search }`; -const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:ConsoleInputProps) => { +const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, height='100%'}:ConsoleInputProps) => { const editorRef = useRef(null); const editorActionsRef = useRef(null); const editorInstanceRef = useRef(null); @@ -167,7 +168,7 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:Con return ( -
+
    { ); const items = [ - { - this.closePopover(); - this.copyAsCurl(); - }} - > - 复制为curl命令 - , { 保存为常用命令 , ]; + if(window.navigator?.clipboard){ + items.unshift( { + this.closePopover(); + this.copyAsCurl(); + }} + > + 复制为curl命令 + ) + } return ( diff --git a/web/src/components/kibana/console/components/request_status_bar/request_status_bar.scss b/web/src/components/kibana/console/components/request_status_bar/request_status_bar.scss index 8432be8e..181d7b20 100644 --- a/web/src/components/kibana/console/components/request_status_bar/request_status_bar.scss +++ b/web/src/components/kibana/console/components/request_status_bar/request_status_bar.scss @@ -10,6 +10,7 @@ .info-item{ margin: 0 12px; position: relative; + flex: 0 0 auto; &.health{ padding-right: 14px; } diff --git a/web/src/components/kibana/console/components/request_status_bar/request_status_bar.tsx b/web/src/components/kibana/console/components/request_status_bar/request_status_bar.tsx index 1a67b5ea..af60bf25 100644 --- a/web/src/components/kibana/console/components/request_status_bar/request_status_bar.tsx +++ b/web/src/components/kibana/console/components/request_status_bar/request_status_bar.tsx @@ -17,12 +17,19 @@ * under the License. */ -import React, { FunctionComponent } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip,EuiCodeBlock } from '@elastic/eui'; -import {HealthStatusCircle} from '@/components/infini/health_status_circle'; -import './request_status_bar.scss'; -import {Drawer, Tabs, Button} from 'antd'; -import { FormattedMessage, formatMessage } from 'umi/locale'; +import React, { FunctionComponent } from "react"; +import { + EuiFlexGroup, + EuiFlexItem, + EuiBadge, + EuiText, + EuiToolTip, + EuiCodeBlock, +} from "@elastic/eui"; +import { HealthStatusCircle } from "@/components/infini/health_status_circle"; +import "./request_status_bar.scss"; +import { Drawer, Tabs, Button } from "antd"; +import { FormattedMessage, formatMessage } from "umi/locale"; export interface Props { requestInProgress: boolean; @@ -48,22 +55,22 @@ export interface Props { const mapStatusCodeToBadgeColor = (statusCode: number) => { if (statusCode <= 199) { - return 'default'; + return "default"; } if (statusCode <= 299) { - return 'secondary'; + return "secondary"; } if (statusCode <= 399) { - return 'primary'; + return "primary"; } if (statusCode <= 499) { - return 'warning'; + return "warning"; } - return 'danger'; + return "danger"; }; export const RequestStatusBar = ({ @@ -71,74 +78,87 @@ export const RequestStatusBar = ({ requestResult, selectedCluster, left, -}:Props) => { +}: Props) => { let content: React.ReactNode = null; - const clusterContent = (
    + const clusterContent = ( +
    - - - + + {" "} + : + + +
    - - {selectedCluster.host} + + : + + {selectedCluster.host}
    - + + : + {selectedCluster.version} -
    -
    ); +
    +
+ ); if (requestInProgress) { content = ( - - Request in progress - + Request in progress ); } else if (requestResult) { - const { endpoint, method, statusCode, statusText, timeElapsedMs } = requestResult; + const { + endpoint, + method, + statusCode, + statusText, + timeElapsedMs, + } = requestResult; content = ( <> -
-
- - {`${method} ${ - endpoint.startsWith('/') ? endpoint : '/' + endpoint - }`} - } - > - - - {/* Use   to ensure that no matter the width we don't allow line breaks */} - {statusCode} - {statusText} - - - -
-
- - +
+ + : + + {`${method} ${ + endpoint.startsWith("/") ? endpoint : "/" + endpoint + }`} + } + > - Time Elapsed + + {/* Use   to ensure that no matter the width we don't allow line breaks */} + {statusCode} - {statusText} + - } - > - - - {timeElapsedMs} {'ms'} - - - -
+
+
+
+ + : + + Time Elapsed} + > + + + {timeElapsedMs} {"ms"} + + + +
); @@ -146,10 +166,11 @@ export const RequestStatusBar = ({ return (
- {left?
{clusterContent}
: - [
{content}
,] - } + {left ? ( +
{clusterContent}
+ ) : ( +
{content}
+ )}
- ); }; diff --git a/web/src/components/kibana/console/hooks/use_send_current_request_to_es/index.ts b/web/src/components/kibana/console/hooks/use_send_current_request_to_es/index.ts index 619ed777..f1687e42 100644 --- a/web/src/components/kibana/console/hooks/use_send_current_request_to_es/index.ts +++ b/web/src/components/kibana/console/hooks/use_send_current_request_to_es/index.ts @@ -62,7 +62,7 @@ export const useSendCurrentRequestToES = () => { } const {url, method, data} = requests[0]; if(method === 'LOAD'){ - const rawUrl = data[0].slice(4).trim(); + const rawUrl = data[0]? data[0].slice(4).trim(): url; const cmd = getCommand(rawUrl); // const curPostion = editor.currentReqRange //(editor.getCoreEditor().getCurrentPosition()); const lineNumber = editor.getCoreEditor().getCurrentPosition().lineNumber; diff --git a/web/src/components/kibana/data/common/index_patterns/index_patterns/index_patterns.ts b/web/src/components/kibana/data/common/index_patterns/index_patterns/index_patterns.ts index 4882d55a..f0e638f2 100644 --- a/web/src/components/kibana/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/web/src/components/kibana/data/common/index_patterns/index_patterns/index_patterns.ts @@ -17,14 +17,14 @@ * under the License. */ -import { SavedObjectsClientCommon } from '../..'; +import { SavedObjectsClientCommon } from "../.."; -import { createIndexPatternCache } from '.'; -import { IndexPattern } from './index_pattern'; +import { createIndexPatternCache } from "."; +import { IndexPattern } from "./index_pattern"; import { createEnsureDefaultIndexPattern, EnsureDefaultIndexPattern, -} from './ensure_default_index_pattern'; +} from "./ensure_default_index_pattern"; import { OnNotification, OnError, @@ -37,17 +37,17 @@ import { FieldSpec, FieldFormatMap, IndexPatternFieldMap, -} from '../types'; -import { FieldFormatsStartCommon } from '../../field_formats'; -import { UI_SETTINGS, SavedObject } from '../../../common'; -import { SavedObjectNotFound } from '../../../../kibana_utils/common'; -import { IndexPatternMissingIndices } from '../lib'; -import { findByTitle } from '../utils'; -import { DuplicateIndexPatternError } from '../errors'; +} from "../types"; +import { FieldFormatsStartCommon } from "../../field_formats"; +import { UI_SETTINGS, SavedObject } from "../../../common"; +import { SavedObjectNotFound } from "../../../../kibana_utils/common"; +import { IndexPatternMissingIndices } from "../lib"; +import { findByTitle } from "../utils"; +import { DuplicateIndexPatternError } from "../errors"; const indexPatternCache = createIndexPatternCache(); const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; -const savedObjectType = 'view'; +const savedObjectType = "view"; export interface IndexPatternSavedObjectAttrs { title: string; @@ -67,7 +67,9 @@ interface IndexPatternsServiceDeps { export class IndexPatternsService { private config: UiSettingsCommon; private savedObjectsClient: SavedObjectsClientCommon; - private savedObjectsCache?: Array> | null; + private savedObjectsCache?: Array< + SavedObject + > | null; private apiClient: IIndexPatternsApiClient; private fieldFormats: FieldFormatsStartCommon; private onNotification: OnNotification; @@ -102,9 +104,11 @@ export class IndexPatternsService { * Refresh cache of index pattern ids and titles */ private async refreshSavedObjectsCache() { - this.savedObjectsCache = await this.savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title'], + this.savedObjectsCache = await this.savedObjectsClient.find< + IndexPatternSavedObjectAttrs + >({ + type: "index-pattern", + fields: ["title"], perPage: 10000, }); } @@ -180,7 +184,7 @@ export class IndexPatternsService { * Get default index pattern */ getDefault = async () => { - const defaultIndexPatternId = await this.config.get('defaultIndex'); + const defaultIndexPatternId = await this.config.get("defaultIndex"); if (defaultIndexPatternId) { return await this.get(defaultIndexPatternId); } @@ -194,8 +198,8 @@ export class IndexPatternsService { * @param force */ setDefault = async (id: string, force = false) => { - if (force || !this.config.get('defaultIndex')) { - await this.config.set('defaultIndex', id); + if (force || !this.config.get("defaultIndex")) { + await this.config.set("defaultIndex", id); } }; @@ -206,10 +210,10 @@ export class IndexPatternsService { return Object.values(specs).every((spec) => { // See https://github.com/elastic/kibana/pull/8421 - const hasFieldCaps = 'aggregatable' in spec && 'searchable' in spec; + const hasFieldCaps = "aggregatable" in spec && "searchable" in spec; // See https://github.com/elastic/kibana/pull/11969 - const hasDocValuesFlag = 'readFromDocValues' in spec; + const hasDocValuesFlag = "readFromDocValues" in spec; return !hasFieldCaps || !hasDocValuesFlag; }); @@ -251,15 +255,22 @@ export class IndexPatternsService { refreshFields = async (indexPattern: IndexPattern) => { try { const fields = await this.getFieldsForIndexPattern(indexPattern); - const scripted = indexPattern.getScriptedFields().map((field) => field.spec); + const scripted = indexPattern + .getScriptedFields() + .map((field) => field.spec); indexPattern.fields.replaceAll([...fields, ...scripted]); } catch (err) { if (err instanceof IndexPatternMissingIndices) { - this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); + this.onNotification({ + title: (err as any).message, + color: "danger", + iconType: "alert", + }); } this.onError(err, { - title: 'Error fetching fields for index pattern {indexPattern.title} (ID: {indexPattern.id})', + title: + "Error fetching fields for index pattern {indexPattern.title} (ID: {indexPattern.id})", }); } }; @@ -277,18 +288,24 @@ export class IndexPatternsService { title: string, options: GetFieldsOptions ) => { - const scriptdFields = Object.values(fields).filter((field) => field.scripted); + const scriptdFields = Object.values(fields).filter( + (field) => field.scripted + ); try { const newFields = await this.getFieldsForWildcard(options); return this.fieldArrayToMap([...newFields, ...scriptdFields]); } catch (err) { if (err instanceof IndexPatternMissingIndices) { - this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); + this.onNotification({ + title: (err as any).message, + color: "danger", + iconType: "alert", + }); return {}; } this.onError(err, { - title: `Error fetching fields for index pattern ${title} (ID: ${id})` + title: `Error fetching fields for index pattern ${title} (ID: ${id})`, }); } return fields; @@ -299,7 +316,10 @@ export class IndexPatternsService { * @param fieldSpecs * @param fieldFormatMap */ - private addFormatsToFields = (fieldSpecs: FieldSpec[], fieldFormatMap: FieldFormatMap) => { + private addFormatsToFields = ( + fieldSpecs: FieldSpec[], + fieldFormatMap: FieldFormatMap + ) => { Object.entries(fieldFormatMap).forEach(([fieldName, value]) => { const field = fieldSpecs.find((fld: FieldSpec) => fld.name === fieldName); if (field) { @@ -323,7 +343,9 @@ export class IndexPatternsService { * @param savedObject */ - savedObjectToSpec = (savedObject: SavedObject): IndexPatternSpec => { + savedObjectToSpec = ( + savedObject: SavedObject + ): IndexPatternSpec => { const { id, version, @@ -336,13 +358,17 @@ export class IndexPatternsService { sourceFilters, fieldFormatMap, typeMeta, - type, }, + type, } = savedObject; - const parsedSourceFilters = sourceFilters ? JSON.parse(sourceFilters) : undefined; + const parsedSourceFilters = sourceFilters + ? JSON.parse(sourceFilters) + : undefined; const parsedTypeMeta = typeMeta ? JSON.parse(typeMeta) : undefined; - const parsedFieldFormatMap = fieldFormatMap ? JSON.parse(fieldFormatMap) : {}; + const parsedFieldFormatMap = fieldFormatMap + ? JSON.parse(fieldFormatMap) + : {}; const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : []; this.addFormatsToFields(parsedFields, parsedFieldFormatMap); @@ -365,16 +391,20 @@ export class IndexPatternsService { * @param id */ - get = async (id: string): Promise => { - const cache = indexPatternCache.get(id); + get = async ( + id: string, + typ: string, + clusterID: string + ): Promise => { + const cacheID = typ == "index" ? clusterID + id : id; + const cache = indexPatternCache.get(cacheID); if (cache) { return cache; } - const savedObject = await this.savedObjectsClient.get( - savedObjectType, - id - ); + const savedObject = await this.savedObjectsClient.get< + IndexPatternAttributes + >(typ || savedObjectType, id); // if (!savedObject.version) { // throw new SavedObjectNotFound(savedObjectType, id, 'management/kibana/indexPatterns'); @@ -382,28 +412,34 @@ export class IndexPatternsService { const spec = this.savedObjectToSpec(savedObject); const { title, type, typeMeta } = spec; - const parsedFieldFormats: FieldFormatMap = savedObject.attributes.fieldFormatMap + const parsedFieldFormats: FieldFormatMap = savedObject.attributes + .fieldFormatMap ? JSON.parse(savedObject.attributes.fieldFormatMap) : {}; - - const isFieldRefreshRequired = this.isFieldRefreshRequired(spec.fields); + const isFieldRefreshRequired = + spec.type == "index" ? false : this.isFieldRefreshRequired(spec.fields); let isSaveRequired = isFieldRefreshRequired; try { spec.fields = isFieldRefreshRequired - ? await this.refreshFieldSpecMap(spec.fields || {}, id, spec.title as string, { - pattern: title, - metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), - type, - params: typeMeta && typeMeta.params, - }) + ? await this.refreshFieldSpecMap( + spec.fields || {}, + id, + spec.title as string, + { + pattern: title, + metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), + type, + params: typeMeta && typeMeta.params, + } + ) : spec.fields; } catch (err) { isSaveRequired = false; if (err instanceof IndexPatternMissingIndices) { this.onNotification({ title: (err as any).message, - color: 'danger', - iconType: 'alert', + color: "danger", + iconType: "alert", }); } else { this.onError(err, { @@ -468,8 +504,13 @@ export class IndexPatternsService { * @param spec * @param skipFetchFields */ - async create(spec: IndexPatternSpec, skipFetchFields = false): Promise { - const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); + async create( + spec: IndexPatternSpec, + skipFetchFields = false + ): Promise { + const shortDotsEnable = await this.config.get( + UI_SETTINGS.SHORT_DOTS_ENABLE + ); const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); const indexPattern = new IndexPattern({ @@ -488,11 +529,13 @@ export class IndexPatternsService { } find = async (search: string, size: number = 10): Promise => { - const savedObjects = await this.savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title'], + const savedObjects = await this.savedObjectsClient.find< + IndexPatternSavedObjectAttrs + >({ + type: "index-pattern", + fields: ["title"], search, - searchFields: ['title'], + searchFields: ["title"], perPage: size, }); const getIndexPatternPromises = savedObjects.map(async (savedObject) => { @@ -508,7 +551,11 @@ export class IndexPatternsService { * @param skipFetchFields */ - async createAndSave(spec: IndexPatternSpec, override = false, skipFetchFields = false) { + async createAndSave( + spec: IndexPatternSpec, + override = false, + skipFetchFields = false + ) { const indexPattern = await this.create(spec, skipFetchFields); await this.createSavedObject(indexPattern, override); await this.setDefault(indexPattern.id as string); @@ -527,14 +574,20 @@ export class IndexPatternsService { if (override) { await this.delete(dupe.id); } else { - throw new DuplicateIndexPatternError(`Duplicate index pattern: ${indexPattern.title}`); + throw new DuplicateIndexPatternError( + `Duplicate index pattern: ${indexPattern.title}` + ); } } const body = indexPattern.getAsSavedObjectBody(); - const response = await this.savedObjectsClient.create(savedObjectType, body, { - id: indexPattern.id, - }); + const response = await this.savedObjectsClient.create( + savedObjectType, + body, + { + id: indexPattern.id, + } + ); indexPattern.id = response.id; indexPatternCache.set(indexPattern.id, indexPattern); return indexPattern; @@ -551,6 +604,7 @@ export class IndexPatternsService { saveAttempts: number = 0, ignoreErrors: boolean = false ): Promise { + debugger; if (!indexPattern.id) return; // get the list of attributes @@ -566,13 +620,18 @@ export class IndexPatternsService { }); return this.savedObjectsClient - .update(savedObjectType, indexPattern.id, body, { version: indexPattern.version }) + .update(savedObjectType, indexPattern.id, body, { + version: indexPattern.version, + }) .then((resp) => { indexPattern.id = resp.id; indexPattern.version = resp.version; }) .catch(async (err) => { - if (err?.res?.status === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) { + if ( + err?.res?.status === 409 && + saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS + ) { const samePattern = await this.get(indexPattern.id as string); // What keys changed from now and what the server returned const updatedBody = samePattern.getAsSavedObjectBody(); @@ -584,7 +643,10 @@ export class IndexPatternsService { const serverChangedKeys: string[] = []; Object.entries(updatedBody).forEach(([key, value]) => { - if (value !== (body as any)[key] && value !== (originalBody as any)[key]) { + if ( + value !== (body as any)[key] && + value !== (originalBody as any)[key] + ) { serverChangedKeys.push(key); } }); @@ -603,10 +665,10 @@ export class IndexPatternsService { if (ignoreErrors) { return; } - const title = - 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.'; + const title = + "Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern."; - this.onNotification({ title, color: 'danger' }); + this.onNotification({ title, color: "danger" }); throw err; } @@ -620,7 +682,11 @@ export class IndexPatternsService { indexPatternCache.clear(indexPattern.id!); // Try the save again - return this.updateSavedObject(indexPattern, saveAttempts, ignoreErrors); + return this.updateSavedObject( + indexPattern, + saveAttempts, + ignoreErrors + ); } throw err; }); diff --git a/web/src/components/kibana/data/public/ui/filter_bar/filter_bar.tsx b/web/src/components/kibana/data/public/ui/filter_bar/filter_bar.tsx index 6d4ad740..3029c6d0 100644 --- a/web/src/components/kibana/data/public/ui/filter_bar/filter_bar.tsx +++ b/web/src/components/kibana/data/public/ui/filter_bar/filter_bar.tsx @@ -185,7 +185,7 @@ function FilterBarUI(props: Props) { alignItems="flexStart" responsive={false} > - + = ({ columns, hits, sortOrder, indexPattern, onFilter, onMoveColumn, onAddColumn, - onRemoveColumn, onChangeSortOrder, document }) => { - const [scrollState, setScrollState] = useState({limit: pageCount, hasMore: true}); - useEffect(()=>{ +const Table: React.FC = ({ + columns, + hits, + sortOrder, + indexPattern, + onFilter, + onMoveColumn, + onAddColumn, + onRemoveColumn, + onChangeSortOrder, + document, +}) => { + const [scrollState, setScrollState] = useState({ + limit: pageCount, + hasMore: true, + }); + useEffect(() => { setScrollState({ - limit: pageCount, + limit: pageCount, hasMore: hits.length > pageCount, - }) - },[indexPattern, hits]) - + }); + }, [indexPattern, hits]); + const tableRef = React.useRef(null); + return ( { + next={() => { const newLimit = scrollState.limit + pageCount; - setScrollState({limit: newLimit, hasMore: newLimit < hits.length}); + setScrollState({ limit: newLimit, hasMore: newLimit < hits.length }); }} hasMore={scrollState.hasMore} - loader={

Loading...

} + loader={ +

Loading...

+ } endMessage={ -

- {/* no more data */} -

- }> -
+

{/* no more data */}

+ } + > +
{hits.length ? (
- - - - - - {hits.slice(0, scrollState.limit).map((row, idx)=>{ - - return +
+ + - })} - -
+ + + {hits.slice(0, scrollState.limit).map((row, idx) => { + return ( + + ); + })} + + +
) : null} @@ -89,8 +112,8 @@ const Table: React.FC = ({ columns, hits, sortOrder, indexPattern, o
) : null}
-
+ ); }; -export default Table; +export default React.memo(Table); diff --git a/web/src/components/kibana/discover/public/application/components/discover_table/table_context.tsx b/web/src/components/kibana/discover/public/application/components/discover_table/table_context.tsx new file mode 100644 index 00000000..a3426187 --- /dev/null +++ b/web/src/components/kibana/discover/public/application/components/discover_table/table_context.tsx @@ -0,0 +1,7 @@ +import * as React from "react"; + +const TableContext = React.createContext({ + tableRef: null, +}); + +export default TableContext; diff --git a/web/src/components/kibana/discover/public/application/components/discover_table/table_row/_details.scss b/web/src/components/kibana/discover/public/application/components/discover_table/table_row/_details.scss index a700107f..69f91914 100644 --- a/web/src/components/kibana/discover/public/application/components/discover_table/table_row/_details.scss +++ b/web/src/components/kibana/discover/public/application/components/discover_table/table_row/_details.scss @@ -8,7 +8,6 @@ // Overwrite the border on the bootstrap table .kbnDocTableDetails__row { - > td { // Offsets negative margins from an inner flex group padding: 16px !important; @@ -22,4 +21,3 @@ border-top: none !important; } } - diff --git a/web/src/components/kibana/discover/public/application/components/discover_table/table_row/detail.tsx b/web/src/components/kibana/discover/public/application/components/discover_table/table_row/detail.tsx index c6ef8e78..eecd1522 100644 --- a/web/src/components/kibana/discover/public/application/components/discover_table/table_row/detail.tsx +++ b/web/src/components/kibana/discover/public/application/components/discover_table/table_row/detail.tsx @@ -1,15 +1,29 @@ -import {EuiIcon} from '@elastic/eui'; -import { DocViewer } from '../../doc_viewer/doc_viewer'; -import {Drawer, Button, Menu,Dropdown, Icon, Popconfirm, message,Descriptions, Popover, Input} from 'antd'; +import { EuiIcon } from "@elastic/eui"; +import { DocViewer } from "../../doc_viewer/doc_viewer"; +import { + Drawer, + Button, + Menu, + Dropdown, + Icon, + Popconfirm, + message, + Descriptions, + Popover, + Input, +} from "antd"; import Editor from "@monaco-editor/react"; -import {useState, useRef} from 'react'; +import { useState, useRef } from "react"; function generateNewID(id: string) { - return id.slice(0, 14) + Math.random().toString(36).substr(2, 6) + return ( + id.slice(0, 14) + + Math.random() + .toString(36) + .substr(2, 6) + ); } - - interface Props { columns: string[]; indexPattern: any; @@ -28,28 +42,28 @@ export function Detail({ onAddColumn, onRemoveColumn, document, -}:Props){ +}: Props) { const [editorVisible, setEditorVisble] = useState(false); const editorRef = useRef(null); function handleEditorDidMount(editor, monaco) { - editorRef.current = editor; + editorRef.current = editor; } - const editDocumentClick = ()=>{ - setEditorVisble(true) - } - const editCancelClick = ()=>{ - setEditorVisble(false) - } - const saveDocumentClick = async (docID?: string)=>{ + const editDocumentClick = () => { + setEditorVisble(true); + }; + const editCancelClick = () => { + setEditorVisble(false); + }; + const saveDocumentClick = async (docID?: string) => { const value = editorRef.current?.getValue(); - let source = {} + let source = {}; try { - source = JSON.parse(value) + source = JSON.parse(value); } catch (error) { - message.error('wrong json format') - return + message.error("wrong json format"); + return; } let params = { _index: row._index, @@ -57,18 +71,18 @@ export function Detail({ _type: row._type, _source: source, }; - - docID && (params['is_new'] = '1') - const res = await document.saveDocument(params) - if(!res.error) setEditorVisble(false) - } - const deleteDocumentClick = ()=>{ + + docID && (params["is_new"] = "1"); + const res = await document.saveDocument(params); + if (!res.error) setEditorVisble(false); + }; + const deleteDocumentClick = () => { document.deleteDocument({ _index: row._index, _id: row._id, _type: row._type, - }) - } + }); + }; const menu = ( @@ -76,72 +90,117 @@ export function Detail({ Edit - { - deleteDocumentClick(); - }}> Delete + { + deleteDocumentClick(); + }} + > + Delete + ); return ( - -
-
-
-
- -
-
-

Expanded document

-
-
-
-
-
- {setEditorVisble(false)}}> - - {row._index} - {row._id} - - -
-
- - {/* */} - - + +
-
-
-
- -
- - - - ) +
+ +
+ + ); } -const SaveAsNewButton = ({docID, saveDocumentClick}:any)=>{ +const SaveAsNewButton = ({ docID, saveDocumentClick }: any) => { const newID = generateNewID(docID); - const [newDocID, setNewDocID] = useState(newID) - const content = (
-
{ - setNewDocID(e.target.value) - }} />
-
-
) + const [newDocID, setNewDocID] = useState(newID); + const content = ( +
+
+ { + setNewDocID(e.target.value); + }} + /> +
+
+ +
+
+ ); return ( - - - ) -} \ No newline at end of file + content={content} + title="Please input new ID" + trigger="click" + // visible={this.state.visible} + // onVisibleChange={this.handleVisibleChange} + > + + + ); +}; diff --git a/web/src/components/kibana/discover/public/application/components/discover_table/table_row/table_row.tsx b/web/src/components/kibana/discover/public/application/components/discover_table/table_row/table_row.tsx index 8f6971b7..d599f428 100644 --- a/web/src/components/kibana/discover/public/application/components/discover_table/table_row/table_row.tsx +++ b/web/src/components/kibana/discover/public/application/components/discover_table/table_row/table_row.tsx @@ -1,7 +1,9 @@ -import {Open} from './open'; -import {Cell} from './cell'; -import {Detail} from './detail'; -import {useState} from 'react'; +import { Open } from "./open"; +import { Cell } from "./cell"; +import { Detail } from "./detail"; +import React, { useState } from "react"; + +const MemoDetail = React.memo(Detail); interface Props { // sorting="sorting" @@ -25,62 +27,89 @@ export function TableRow({ onAddColumn, onRemoveColumn, row, - document -}:Props){ + document, +}: Props) { const mapping = indexPattern.fields.getByName; - const [open,setOpen] = useState(false); + const [open, setOpen] = useState(false); + return ( <> - - { - setOpen(!open); - }}/> - {(indexPattern.timeFieldName && !hideTimeColumn)? : null} + + { + setOpen(!open); + }} + /> + {indexPattern.timeFieldName && !hideTimeColumn ? ( + + ) : null} - {columns.map(function (column: any) { - const isFilterable = mapping(column) && mapping(column).filterable ;//&& $scope.filter; - return + {columns.map(function(column: any) { + const isFilterable = mapping(column) && mapping(column).filterable; //&& $scope.filter; + return ( + + ); })} - - - {open? - - :null - } + + + {open && ( + + + + )} - ) + ); } const MIN_LINE_LENGTH = 20; -function _displayField(indexPattern:any, row: any, fieldName: string, truncate = false) { +function _displayField( + indexPattern: any, + row: any, + fieldName: string, + truncate = false +) { const text = indexPattern.formatField(row, fieldName); if (truncate && text.length > MIN_LINE_LENGTH) { - return
-
+ return ( +
+ ); } - return ; -} \ No newline at end of file + return ; +} diff --git a/web/src/components/kibana/discover/public/application/components/doc_viewer/doc_viewer.tsx b/web/src/components/kibana/discover/public/application/components/doc_viewer/doc_viewer.tsx index 79809bc6..cff88e4b 100644 --- a/web/src/components/kibana/discover/public/application/components/doc_viewer/doc_viewer.tsx +++ b/web/src/components/kibana/discover/public/application/components/doc_viewer/doc_viewer.tsx @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import './doc_viewer.scss'; -import React from 'react'; -import { EuiTabbedContent } from '@elastic/eui'; -import { getDocViewsRegistry } from '../../../kibana_services'; -import { DocViewerTab } from './doc_viewer_tab'; -import { DocView, DocViewRenderProps } from '../../doc_views/doc_views_types'; +import "./doc_viewer.scss"; +import React from "react"; +import { EuiTabbedContent } from "@elastic/eui"; +import { getDocViewsRegistry } from "../../../kibana_services"; +import { DocViewerTab } from "./doc_viewer_tab"; +import { DocView, DocViewRenderProps } from "../../doc_views/doc_views_types"; +import TableContext from "../discover_table/table_context"; /** * Rendering tabs with different views of 1 Elasticsearch hit in Discover. @@ -54,9 +55,23 @@ export function DocViewer(renderProps: DocViewRenderProps) { // This condition takes care of unit tests with 0 tabs. return null; } + const { tableRef } = React.useContext(TableContext); + const [viewerWidth, setViewerWidth] = React.useState( + tableRef?.offsetWidth - 40 + ); + + React.useEffect(() => { + const onResize = () => { + setViewerWidth(tableRef?.offsetWidth - 40); + }; + window.addEventListener("resize", onResize); + return () => { + window.removeEventListener("resize", onResize); + }; + }, []); return ( -
+
); diff --git a/web/src/components/kibana/discover/public/application/components/histogram/histogram.jsx b/web/src/components/kibana/discover/public/application/components/histogram/histogram.jsx index 3eebd8f5..c90dce09 100644 --- a/web/src/components/kibana/discover/public/application/components/histogram/histogram.jsx +++ b/web/src/components/kibana/discover/public/application/components/histogram/histogram.jsx @@ -1,6 +1,6 @@ -import moment, { unitOfTime } from 'moment-timezone'; -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import moment, { unitOfTime } from "moment-timezone"; +import React, { Component } from "react"; +import PropTypes from "prop-types"; import { Axis, @@ -15,28 +15,27 @@ import { BrushEndListener, Theme, LIGHT_THEME, -} from '@elastic/charts'; -import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; -import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; +} from "@elastic/charts"; +import lightEuiTheme from "@elastic/eui/dist/eui_theme_light.json"; +import darkEuiTheme from "@elastic/eui/dist/eui_theme_dark.json"; import "@elastic/charts/dist/theme_light.css"; -import { Subscription, combineLatest } from 'rxjs'; -import {CurrentTime} from './current_time'; +import { Subscription, combineLatest } from "rxjs"; +import { CurrentTime } from "./current_time"; import { Endzones, getAdjustedInterval, renderEndzoneTooltip, -} from './endzones'; - +} from "./endzones"; function getTimezone() { const detectedTimezone = moment.tz.guess(); if (detectedTimezone) return detectedTimezone; - else return moment().format('Z'); + else return moment().format("Z"); } -export class DiscoverHistogram extends Component{ - static propTypes = { +export class DiscoverHistogram extends Component { + static propTypes = { chartData: PropTypes.object, timefilterUpdateHandler: PropTypes.func, }; @@ -62,7 +61,7 @@ export class DiscoverHistogram extends Component{ } } - onBrushEnd = ({ x }) => { + onBrushEnd = ({ x }) => { if (!x) { return; } @@ -71,7 +70,7 @@ export class DiscoverHistogram extends Component{ }; onElementClick = (xInterval) => ([elementData]) => { - const startRange = (elementData)[0].x; + const startRange = elementData[0].x; const range = { from: startRange, @@ -130,28 +129,33 @@ export class DiscoverHistogram extends Component{ ), }; const tooltipProps = { - headerFormatter: renderEndzoneTooltip(xInterval, domainStart, domainEnd, this.formatXValue), + headerFormatter: renderEndzoneTooltip( + xInterval, + domainStart, + domainEnd, + this.formatXValue + ), type: TooltipType.VerticalCursor, }; // const xAxisFormatter = getServices().data.fieldFormats.deserialize( // this.props.chartData.yAxisFormat // ); const xAxisFormatter = { - convert: (value)=>{ + convert: (value) => { return value; - } - } + }, + }; //console.log(data) return ( - + {return xAxisFormatter.convert(value)}} + tickFormat={(value) => { + return xAxisFormatter.convert(value); + }} showGridLines /> - ); } diff --git a/web/src/components/kibana/discover/public/application/components/json_code_block/json_code_block.tsx b/web/src/components/kibana/discover/public/application/components/json_code_block/json_code_block.tsx index 56654623..e3e00e4b 100644 --- a/web/src/components/kibana/discover/public/application/components/json_code_block/json_code_block.tsx +++ b/web/src/components/kibana/discover/public/application/components/json_code_block/json_code_block.tsx @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; -import { EuiCodeBlock } from '@elastic/eui'; -import { DocViewRenderProps } from '../../doc_views/doc_views_types'; +import React from "react"; +import { EuiCodeBlock } from "@elastic/eui"; +import { DocViewRenderProps } from "../../doc_views/doc_views_types"; export function JsonCodeBlock({ hit }: DocViewRenderProps) { - const label = 'Read only JSON view of an elasticsearch document'; + const label = "Read only JSON view of an elasticsearch document"; return ( {JSON.stringify(hit, null, 2)} diff --git a/web/src/components/kibana/discover/public/application/components/sidebar/change_indexpattern.tsx b/web/src/components/kibana/discover/public/application/components/sidebar/change_indexpattern.tsx index 6a0000c6..32c815af 100644 --- a/web/src/components/kibana/discover/public/application/components/sidebar/change_indexpattern.tsx +++ b/web/src/components/kibana/discover/public/application/components/sidebar/change_indexpattern.tsx @@ -24,29 +24,34 @@ import { EuiPopoverTitle, EuiSelectable, EuiButtonEmptyProps, + EuiTabs, + EuiTabbedContent, + EuiTab, + EuiSwitch, } from '@elastic/eui'; import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable'; import { IndexPatternRef } from './types'; + export type ChangeIndexPatternTriggerProps = EuiButtonEmptyProps & { label: string; title?: string; }; -// TODO: refactor to shared component with ../../../../../../../../x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern - export function ChangeIndexPattern({ indexPatternRefs, indexPatternId, onChangeIndexPattern, trigger, selectableProps, + indices, }: { trigger: ChangeIndexPatternTriggerProps; indexPatternRefs: IndexPatternRef[]; - onChangeIndexPattern: (newId: string) => void; + onChangeIndexPattern: (newId: string, typ: string) => void; indexPatternId?: string; selectableProps?: EuiSelectableProps; + indices: string[]; }) { const [isPopoverOpen, setPopoverIsOpen] = useState(false); @@ -68,22 +73,21 @@ export function ChangeIndexPattern({ ); }; - return ( - setPopoverIsOpen(false)} - className="eui-textTruncate" - anchorClassName="eui-textTruncate" - display="block" - panelPaddingSize="s" - ownFocus - > -
- - 选择视图 - - { + setSelectedTabId(id); + }; + const [includeSystemIndex, setIncludeSystemIndex] = useState(false); + + const tabs = React.useMemo(()=>{ + const showIndices = includeSystemIndex ? indices: indices.filter(key=>!key.startsWith(".")); + const tabs = [ + { + id: 'view', + name: 'View', + disabled: false, + content: ( checked) as unknown) as { value: string; }; - onChangeIndexPattern(choice.value); + onChangeIndexPattern(choice.value, 'view'); setPopoverIsOpen(false); }} searchProps={{ @@ -112,7 +116,103 @@ export function ChangeIndexPattern({ {list} )} - + ), + }, + { + id: 'index', + name: 'Index', + disabled: false, + content:( +
+
+ setIncludeSystemIndex(!includeSystemIndex)} + /> +
+ + ({ + label: indexName, + key: indexName, + value: indexName, + checked: indexName === indexPatternId ? 'on' : undefined, + }))} + onChange={(choices) => { + const choice = (choices.find(({ checked }) => checked) as unknown) as { + value: string; + }; + onChangeIndexPattern(choice.value, 'index'); + setPopoverIsOpen(false); + }} + searchProps={{ + compressed: true, + ...(selectableProps ? selectableProps.searchProps : undefined), + }} + > + {(list, search) => ( + <> + {search} + {list} + + )} +
), + }, + ]; + return tabs; + },[selectableProps, indexPatternId, indexPatternRefs, indices, includeSystemIndex]) + + const selectedTabContent = React.useMemo(() => { + return tabs.find((obj) => obj.id === selectedTabId)?.content; + }, [selectedTabId, tabs]); + + const renderTabs = () => { + return tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + > + {tab.name} + + )); + }; + + return ( + setPopoverIsOpen(false)} + className="eui-textTruncate" + anchorClassName="eui-textTruncate" + display="block" + panelPaddingSize="s" + ownFocus + > +
+ {/* + 选择视图 + */} + {/* + {renderTabs()} + */} + { + const idx = tabs.findIndex(item=>item.id == tab.id); + setSelectedTabId(idx); + }} + /> + {/*
+ {selectedTabContent} */}
); diff --git a/web/src/components/kibana/discover/public/application/components/sidebar/discover_field.tsx b/web/src/components/kibana/discover/public/application/components/sidebar/discover_field.tsx index 92accd66..e0b2aa3b 100644 --- a/web/src/components/kibana/discover/public/application/components/sidebar/discover_field.tsx +++ b/web/src/components/kibana/discover/public/application/components/sidebar/discover_field.tsx @@ -16,15 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState } from 'react'; -import { EuiPopover, EuiPopoverTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import { DiscoverFieldDetails } from './discover_field_details'; -import { FieldIcon, FieldButton } from '../../../../../kibana_react/public'; -import { FieldDetails } from './types'; -import { IndexPatternField, IndexPattern } from '../../../../../data/public'; -import { shortenDottedString } from '../../helpers'; -import { getFieldTypeName } from './lib/get_field_type_name'; -import './discover_field.scss'; +import React, { useState } from "react"; +import { + EuiPopover, + EuiPopoverTitle, + EuiButtonIcon, + EuiToolTip, +} from "@elastic/eui"; +import { DiscoverFieldDetails } from "./discover_field_details"; +import { FieldIcon, FieldButton } from "../../../../../kibana_react/public"; +import { FieldDetails } from "./types"; +import { IndexPatternField, IndexPattern } from "../../../../../data/public"; +import { shortenDottedString } from "../../helpers"; +import { getFieldTypeName } from "./lib/get_field_type_name"; +import "./discover_field.scss"; export interface DiscoverFieldProps { /** @@ -42,7 +47,11 @@ export interface DiscoverFieldProps { /** * Callback to add a filter to filter bar */ - onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; + onAddFilter: ( + field: IndexPatternField | string, + value: string, + type: "+" | "-" + ) => void; /** * Callback to remove/deselect a the field * @param fieldName @@ -72,8 +81,8 @@ export function DiscoverField({ selected, useShortDots, }: DiscoverFieldProps) { - const addLabelAria = `Add ${field.name } to table`; - const removeLabelAria = `Remove ${field.name } to table`; + const addLabelAria = `Add ${field.name} to table`; + const removeLabelAria = `Remove ${field.name} to table`; const [infoIsOpen, setOpen] = useState(false); @@ -93,11 +102,15 @@ export function DiscoverField({ // u200B is a non-width white-space character, which allows // the browser to efficiently word-wrap right after the dot // without us having to draw a lot of extra DOM elements, etc - return str ? str.replace(/\./g, '.\u200B') : ''; + return str ? str.replace(/\./g, ".\u200B") : ""; } const dscFieldIcon = ( - + ); const fieldName = ( @@ -106,22 +119,21 @@ export function DiscoverField({ title={field.name} className="dscSidebarField__name" > - {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)} + {useShortDots + ? wrapOnDot(shortenDottedString(field.name)) + : wrapOnDot(field.displayName)} ); let actionButton; - if (field.name !== '_source' && !selected) { + if (field.name !== "_source" && !selected) { actionButton = ( - + ) => { - if (ev.type === 'click') { + if (ev.type === "click") { ev.currentTarget.focus(); } ev.preventDefault(); @@ -133,18 +145,15 @@ export function DiscoverField({ /> ); - } else if (field.name !== '_source' && selected) { + } else if (field.name !== "_source" && selected) { actionButton = ( - + ) => { - if (ev.type === 'click') { + if (ev.type === "click") { ev.currentTarget.focus(); } ev.preventDefault(); @@ -158,7 +167,7 @@ export function DiscoverField({ ); } - if (field.type === '_source') { + if (field.type === "_source") { return ( - - {' '} - { 'Top 5 values'} - + {"Top 5 values"} {infoIsOpen && ( void; + setIndexPattern: (id: string, typ: string) => void; + indices: string[]; } /** @@ -44,6 +45,7 @@ export function DiscoverIndexPattern({ indexPatternList, selectedIndexPattern, setIndexPattern, + indices, }: DiscoverIndexPatternProps) { const options: IndexPatternRef[] = (indexPatternList || []).map((entity) => ({ id: entity.id, @@ -75,13 +77,27 @@ export function DiscoverIndexPattern({ }} indexPatternId={selected.id} indexPatternRefs={options} - onChangeIndexPattern={(id) => { - const indexPattern = options.find((pattern) => pattern.id === id); + onChangeIndexPattern={(id, typ) => { + let indexPattern = null; + if(typ == 'index'){ + indices.forEach((indexName)=>{ + if(indexName == id){ + indexPattern = { + id: indexName, + title: indexName, + viewName: indexName, + } + } + }) + }else{ + indexPattern = options.find((pattern) => pattern.id === id); + } if (indexPattern) { - setIndexPattern(id); + setIndexPattern(id, typ); setSelected(indexPattern); } }} + indices={indices} />
); diff --git a/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.scss b/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.scss index 1c0ec048..21af71fa 100644 --- a/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.scss +++ b/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.scss @@ -1,4 +1,4 @@ -@import '../../../../../core/public/variables.scss'; +@import "../../../../../core/public/variables.scss"; .dscSidebar__container { padding-left: 0 !important; padding-right: 0 !important; @@ -26,11 +26,17 @@ .dscFieldListHeader { padding: $euiSizeS $euiSizeS 0 $euiSizeS; - background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade); + background-color: lightOrDarkTheme( + tint($euiColorPrimary, 90%), + $euiColorLightShade + ); } .dscFieldList--popular { - background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade); + background-color: lightOrDarkTheme( + tint($euiColorPrimary, 90%), + $euiColorLightShade + ); } .dscFieldChooser { @@ -45,7 +51,7 @@ .dscSidebarItem { &:hover, &:focus-within, - &[class*='-isActive'] { + &[class*="-isActive"] { .dscSidebarItem__action { opacity: 1; } @@ -95,3 +101,23 @@ color: $euiTextColor; margin-bottom: $euiSizeS; } + +#fields-tree-wrapper { + .ant-tree-switcher-noop { + display: none; + } + .ant-tree-treenode-switcher-close { + padding: 0; + .ant-tree-node-content-wrapper { + height: 32px; + } + .ant-tree-title { + display: block; + height: 32px; + .kbnFieldButton__button { + padding: 0; + align-items: center; + } + } + } +} diff --git a/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.tsx b/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.tsx index 3548e6c2..8fd735ba 100644 --- a/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/web/src/components/kibana/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -16,22 +16,27 @@ * specific language governing permissions and limitations * under the License. */ -import './discover_sidebar.scss'; -import React, { useCallback, useEffect, useState, useMemo } from 'react'; -import { EuiButtonIcon, EuiTitle, EuiSpacer,EuiHideFor } from '@elastic/eui'; -import { sortBy } from 'lodash'; -import { DiscoverField } from './discover_field'; -import { DiscoverIndexPattern } from './discover_index_pattern'; -import { DiscoverFieldSearch } from './discover_field_search'; -import { IndexPatternAttributes } from '../../../../../data/common'; -import { SavedObject } from '../../../../../../core/types'; +import "./discover_sidebar.scss"; +import React, { useCallback, useEffect, useState, useMemo } from "react"; +import { EuiButtonIcon, EuiTitle, EuiSpacer, EuiHideFor } from "@elastic/eui"; +import { sortBy } from "lodash"; +import { DiscoverField } from "./discover_field"; +import { DiscoverIndexPattern } from "./discover_index_pattern"; +import { DiscoverFieldSearch } from "./discover_field_search"; +import { IndexPatternAttributes } from "../../../../../data/common"; +import { SavedObject } from "../../../../../../core/types"; // import { FIELDS_LIMIT_SETTING } from '../../../../common'; -import { groupFields } from './lib/group_fields'; -import { IndexPatternField, IndexPattern, UI_SETTINGS } from '../../../../../data/public'; -import { getDetails } from './lib/get_details'; -import { getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter'; -import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list'; +import { groupFields } from "./lib/group_fields"; +import { + IndexPatternField, + IndexPattern, + UI_SETTINGS, +} from "../../../../../data/public"; +import { getDetails } from "./lib/get_details"; +import { getDefaultFieldFilter, setFieldFilterProp } from "./lib/field_filter"; +import { getIndexPatternFieldList } from "./lib/get_index_pattern_field_list"; // import { getServices } from '../../../kibana_services'; +import { Tree, Icon } from "antd"; export interface DiscoverSidebarProps { /** @@ -57,7 +62,11 @@ export interface DiscoverSidebarProps { /** * Callback function when adding a filter from sidebar */ - onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; + onAddFilter: ( + field: IndexPatternField | string, + value: string, + type: "+" | "-" + ) => void; /** * Callback function when removing a field * @param fieldName @@ -72,6 +81,7 @@ export interface DiscoverSidebarProps { */ setIndexPattern: (id: string) => void; isClosed: boolean; + indices: string[]; } export function DiscoverSidebar({ @@ -85,16 +95,22 @@ export function DiscoverSidebar({ selectedIndexPattern, setIndexPattern, isClosed, + indices, }: DiscoverSidebarProps) { const [showFields, setShowFields] = useState(false); const [fields, setFields] = useState(null); - const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter()); + const [fieldFilterState, setFieldFilterState] = useState( + getDefaultFieldFilter() + ); // const services = useMemo(() => getServices(), []); useEffect(() => { - const newFields = getIndexPatternFieldList(selectedIndexPattern, fieldCounts); + const newFields = getIndexPatternFieldList( + selectedIndexPattern, + fieldCounts + ); setFields(newFields); - }, [selectedIndexPattern, fieldCounts, hits, ]);//services + }, [selectedIndexPattern, fieldCounts, hits]); //services const onChangeFieldSearch = useCallback( (field: string, value: string | boolean | undefined) => { @@ -105,27 +121,51 @@ export function DiscoverSidebar({ ); const getDetailsByField = useCallback( - (ipField: IndexPatternField) => getDetails(ipField, hits, columns, selectedIndexPattern), + (ipField: IndexPatternField) => + getDetails(ipField, hits, columns, selectedIndexPattern), [hits, columns, selectedIndexPattern] ); - const popularLimit = 5;//services.uiSettings.get(FIELDS_LIMIT_SETTING); - const useShortDots = false;//services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE); + const popularLimit = 5; //services.uiSettings.get(FIELDS_LIMIT_SETTING); + const useShortDots = false; //services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const { selected: selectedFields, popular: popularFields, unpopular: unpopularFields, - } = useMemo(() => groupFields(fields, columns, popularLimit, fieldCounts, fieldFilterState), [ - fields, - columns, - popularLimit, - fieldCounts, - fieldFilterState, - ]); + fieldsTree, + } = useMemo(() => { + const groupedFields = groupFields( + fields, + columns, + popularLimit, + fieldCounts, + fieldFilterState + ); + const fieldsTree = {}; + groupedFields.unpopular.forEach((field) => { + const keys = field.displayName.split("."); + let currentObj = fieldsTree; + keys.forEach((key: string, i: number) => { + if (!currentObj[key]) { + currentObj[key] = {}; + } + if (keys.length == i + 1) { + field.isLeaf = true; + currentObj[key] = field; + return; + } + currentObj = currentObj[key]; + }); + }); + return { + ...groupedFields, + fieldsTree, + }; + }, [fields, columns, popularLimit, fieldCounts, fieldFilterState]); const fieldTypes = useMemo(() => { - const result = ['any']; + const result = ["any"]; if (Array.isArray(fields)) { for (const field of fields) { if (result.indexOf(field.type) === -1) { @@ -136,20 +176,56 @@ export function DiscoverSidebar({ return result; }, [fields]); - if (!selectedIndexPattern || !fields || isClosed ) { + if (!selectedIndexPattern || !fields || isClosed) { return null; } + const buildTree = (treeObj: any) => { + return Object.keys(treeObj).map((key) => { + if (treeObj[key].isLeaf) { + return ( + } + selectable={false} + title={ + + } + key={key} + /> + ); + } + return ( + } + selectable={false} + title={key} + key={key} + > + {buildTree(treeObj[key])} + + ); + }); + }; + return ( - -
+ +
o.attributes.viewName)} + indexPatternList={sortBy( + indexPatternList, + (o) => o.attributes.viewName + )} + indices={indices} />
@@ -164,9 +240,7 @@ export function DiscoverSidebar({ {fields.length > 0 && ( <> -

- Selected fields -

+

Selected fields

    - -

    - Available fields -

    + +

    Available fields

    {/*
    -
      )} -
        ); })} -
      +
    */} +
    + + {buildTree(fieldsTree)} + +
-
+ ); } diff --git a/web/src/components/kibana/discover/public/application/components/timechart_header/timechart_header.tsx b/web/src/components/kibana/discover/public/application/components/timechart_header/timechart_header.tsx index eb624f49..a625abb5 100644 --- a/web/src/components/kibana/discover/public/application/components/timechart_header/timechart_header.tsx +++ b/web/src/components/kibana/discover/public/application/components/timechart_header/timechart_header.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback } from "react"; import { EuiFlexGroup, EuiFlexItem, @@ -24,8 +24,8 @@ import { EuiText, EuiSelect, EuiIconTip, -} from '@elastic/eui'; -import moment from 'moment'; +} from "@elastic/eui"; +import moment from "moment"; export interface TimechartHeaderProps { /** @@ -39,6 +39,7 @@ export interface TimechartHeaderProps { scaled?: boolean; description?: string; scale?: number; + timeFieldName: string; }; /** * Range of dates to be displayed @@ -59,6 +60,7 @@ export interface TimechartHeaderProps { * selected interval */ stateInterval: string; + hits: number; } export function TimechartHeader({ @@ -68,12 +70,13 @@ export function TimechartHeader({ options, onChangeInterval, stateInterval, + hits, }: TimechartHeaderProps) { const [interval, setInterval] = useState(stateInterval); const toMoment = useCallback( (datetime: string) => { if (!datetime) { - return ''; + return ""; } if (!dateFormat) { return datetime; @@ -92,27 +95,36 @@ export function TimechartHeader({ onChangeInterval(e.target.value); }; - if (!timeRange || !bucketInterval) { - return null; - } + // if (!timeRange || !bucketInterval) { + // return null; + // } return ( - - - - - {`${toMoment(timeRange.from)} - ${toMoment(timeRange.to)} ${ - interval !== 'auto' - ? 'per' - : '' + + +
+ Found {hits}{" "} + records + {timeRange && ( + + {`between ${toMoment(timeRange.from)} and ${toMoment( + timeRange.to + )} ${ + interval !== "auto" ? "" : "" //per }`} - - - - + + )} + {bucketInterval && ( + {`(${bucketInterval.timeFieldName} per ${bucketInterval.description})`} + )} +
+
+ {/* - -
+
*/} +
); } diff --git a/web/src/components/kibana/discover/public/application/helpers/shorten_dotted_string.ts b/web/src/components/kibana/discover/public/application/helpers/shorten_dotted_string.ts index 9d78a967..d6548db4 100644 --- a/web/src/components/kibana/discover/public/application/helpers/shorten_dotted_string.ts +++ b/web/src/components/kibana/discover/public/application/helpers/shorten_dotted_string.ts @@ -23,4 +23,5 @@ const DOT_PREFIX_RE = /(.).+?\./g; * Convert a dot.notated.string into a short * version (d.n.string) */ -export const shortenDottedString = (input: string) => input.replace(DOT_PREFIX_RE, '$1.'); +export const shortenDottedString = (input: string) => + input.replace(DOT_PREFIX_RE, ""); //'$1.'); diff --git a/web/src/lib/hooks/use_click_outsize.js b/web/src/lib/hooks/use_click_outsize.js new file mode 100644 index 00000000..3e89bcc9 --- /dev/null +++ b/web/src/lib/hooks/use_click_outsize.js @@ -0,0 +1,39 @@ +import { useEffect, useRef } from "react"; + +export function useOnClickOutside(handler) { + const ref = useRef(); + useEffect( + () => { + const listener = (event) => { + if (!ref.current || ref.current.contains(event.target)) { + return; + } + handler(event); + }; + + if (window.PointerEvent) { + document.addEventListener("pointerdown", listener); + } else { + document.addEventListener("mousedown", listener); + document.addEventListener("touchstart", listener); + } + + return () => { + if (window.PointerEvent) { + document.removeEventListener("pointerdown", listener); + } else { + document.removeEventListener("mousedown", listener); + document.removeEventListener("touchstart", listener); + } + }; + }, + // Add ref and handler to effect dependencies + // It's worth noting that because passed in handler is a new ... + // ... function on every render that will cause this effect ... + // ... callback/cleanup to run every render. It's not a big deal ... + // ... but to optimize you can wrap handler in useCallback before ... + // ... passing it into this hook. + [handler] + ); + return [ref]; +} diff --git a/web/src/models/global.js b/web/src/models/global.js index 3f869dd1..b224a8a7 100644 --- a/web/src/models/global.js +++ b/web/src/models/global.js @@ -101,6 +101,7 @@ export default { type: 'saveData', payload: { clusterList: newClusterList, + clusterTotal: res.total, search: { ...search, cluster: payload, diff --git a/web/src/pages/Cluster/NewOverview.js b/web/src/pages/Cluster/NewOverview.js index 9de2664e..42a93192 100644 --- a/web/src/pages/Cluster/NewOverview.js +++ b/web/src/pages/Cluster/NewOverview.js @@ -1,9 +1,13 @@ import * as React from 'react'; -import {Tabs} from 'antd'; +import {Tabs, Row, Col, Card} from 'antd'; import Clusters from './components/clusters'; +import styles from "./Overview.less"; +import {connect} from "dva"; +import {formatter} from '@/lib/format'; const {TabPane} = Tabs; + const panes = [ { title: 'Clusters', component: Clusters, key: 'clusters' }, { title: 'Hosts', component: 'Content of Tab 2', key: 'hosts' }, @@ -11,9 +15,66 @@ const panes = [ {title: 'Indices', component: 'Content of Tab 3',key: 'indices'}, ]; -const NewOverview = ()=>{ +const NewOverview = (props)=>{ + React.useEffect(()=>{ + const {dispatch} = props; + dispatch({ + type: 'clusterOverview/fetchOverview', + }) + },[]) + const {clusterTotal, overview} = props; + + const totalStoreSize = formatter.bytes(overview?.total_store_size_in_bytes || 0); return (
+
+ + + + +
+ {clusterTotal?.value} +
+
+ + + + +
+ {overview?.total_host} +
+
+ + + + +
+ {overview?.total_node} +
+
+ + + + +
+ {totalStoreSize.size || '-'}{totalStoreSize.unit} +
+
+ +
+
{}} @@ -31,4 +92,10 @@ const NewOverview = ()=>{
); } -export default NewOverview; \ No newline at end of file +export default connect(({ + clusterOverview, + global +})=>({ + overview: clusterOverview.overview, + clusterTotal: global.clusterTotal, +}))(NewOverview) \ No newline at end of file diff --git a/web/src/pages/DataManagement/Discover.jsx b/web/src/pages/DataManagement/Discover.jsx index 48a61c16..32ea97a3 100644 --- a/web/src/pages/DataManagement/Discover.jsx +++ b/web/src/pages/DataManagement/Discover.jsx @@ -7,8 +7,8 @@ import React, { createContext, useContext, useRef, -} from 'react'; -import classNames from 'classnames'; +} from "react"; +import classNames from "classnames"; import { EuiDataGrid, @@ -25,56 +25,71 @@ import { EuiPage, EuiPageBody, EuiPageContent, -} from '@elastic/eui'; -import '@elastic/eui/dist/eui_theme_amsterdam_light.css'; -import * as styles from './discover.scss'; -import { Subscription } from 'rxjs'; -import { connect } from 'dva'; + EuiSelect, +} from "@elastic/eui"; +import "@elastic/eui/dist/eui_theme_amsterdam_light.css"; +import * as styles from "./discover.scss"; +import { Subscription } from "rxjs"; +import { connect } from "dva"; -import { Card, Spin, message } from 'antd'; +import { Card, Spin, message, Select, Icon, Popover } from "antd"; // import DiscoverGrid from './Components/discover_grid'; -import {flattenHitWrapper} from '../../components/kibana/data/common/index_patterns/index_patterns'; -import {getStateColumnActions} from '../../components/kibana/discover/public/application/angular/doc_table/actions/columns'; -import { DiscoverSidebar } from '../../components/kibana/discover/public/application/components/sidebar/discover_sidebar'; -import {HitsCounter } from '../../components/kibana/discover/public/application/components/hits_counter'; -import {TimechartHeader } from '../../components/kibana/discover/public/application/components/timechart_header'; -import {DiscoverHistogram} from '../../components/kibana/discover/public/application/components/histogram/histogram'; -import moment from 'moment'; -import {getContext} from './context'; -import {createSearchBar} from '../../components/kibana/data/public/ui/search_bar/create_search_bar'; -import { LoadingSpinner } from '../../components/kibana/discover/public/application/components/loading_spinner/loading_spinner'; -import { DiscoverNoResults } from '../../components/kibana/discover/public/application/angular/directives/no_results'; -import { buildPointSeriesData } from '../../components/kibana/discover/public/application/angular/helpers/'; -import {generateFilters} from '../../components/kibana/data/public/query/filter_manager/lib/generate_filters'; -import Table from '../../components/kibana/discover/public/application/components/discover_table/table'; +import { flattenHitWrapper } from "../../components/kibana/data/common/index_patterns/index_patterns"; +import { getStateColumnActions } from "../../components/kibana/discover/public/application/angular/doc_table/actions/columns"; +import { DiscoverSidebar } from "../../components/kibana/discover/public/application/components/sidebar/discover_sidebar"; +import { HitsCounter } from "../../components/kibana/discover/public/application/components/hits_counter"; +import { TimechartHeader } from "../../components/kibana/discover/public/application/components/timechart_header"; +import { DiscoverHistogram } from "../../components/kibana/discover/public/application/components/histogram/histogram"; +import moment from "moment"; +import { getContext } from "./context"; +import { createSearchBar } from "../../components/kibana/data/public/ui/search_bar/create_search_bar"; +import { LoadingSpinner } from "../../components/kibana/discover/public/application/components/loading_spinner/loading_spinner"; +import { DiscoverNoResults } from "../../components/kibana/discover/public/application/angular/directives/no_results"; +import { buildPointSeriesData } from "../../components/kibana/discover/public/application/angular/helpers/"; +import { generateFilters } from "../../components/kibana/data/public/query/filter_manager/lib/generate_filters"; +import Table from "../../components/kibana/discover/public/application/components/discover_table/table"; +import { SettingContent } from "./SettingContent"; +import TimeFieldExampleImage from "@/assets/time_field_exmaple.jpg"; + +import { + useQueryParam, + StringParam, + QueryParamProvider, + ArrayParam, +} from "use-query-params"; +import { Route } from "umi"; +import { ESPrefix } from "@/services/common"; -import {useQueryParam, StringParam, QueryParamProvider, ArrayParam} from 'use-query-params'; -import {Route} from 'umi'; -import {ESPrefix} from '@/services/common'; - const SidebarMemoized = React.memo(DiscoverSidebar); -const {filterManager, queryStringManager, timefilter, storage, getEsQuery, getSearchParams, - intervalOptions, getTimeBuckets, fetchESRequest, services} = getContext(); +const { + filterManager, + queryStringManager, + timefilter, + storage, + getEsQuery, + getSearchParams, + intervalOptions, + getTimeBuckets, + fetchESRequest, + services, +} = getContext(); const SearchBar = createSearchBar(); +const Discover = (props) => { + const [columnsParam, setColumnsParam] = useQueryParam("columns", ArrayParam); + const [queryParam, setQueryParam] = useQueryParam("query", StringParam); -const Discover = (props)=>{ - - const [columnsParam, setColumnsParam] = useQueryParam('columns', ArrayParam); - const [queryParam, setQueryParam] = useQueryParam('query', StringParam); - - //const indexPatternList = [{"type":"index-pattern","id":"c7fbafd0-34a9-11eb-925f-9db57376c4ce","attributes":{"title":".monitoring-es-7-mb-*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2020-12-02T14:34:38.010Z","version":"WzgyNCw3XQ==","namespaces":["default"],"score":0},{"type":"index-pattern","id":"861ea7f0-3a9b-11eb-9b55-45d33507027a","attributes":{"title":"mock_log*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2020-12-10T04:09:09.044Z","version":"WzE3NTgsMTBd","namespaces":["default"],"score":0},{"type":"index-pattern","id":"1a28c950-0f6b-11eb-9512-2d0c0eda237d","attributes":{"title":"gateway_requests*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2021-05-22T11:04:23.811Z","version":"WzkxMTgsNDhd","namespaces":["default"],"score":0},{"type":"index-pattern","id":"1ccce5c0-bb9a-11eb-957b-939add21a246","attributes":{"title":"test-custom*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2021-05-23T07:40:14.747Z","version":"WzkxOTEsNDhd","namespaces":["default"],"score":0}]; + //const indexPatternList = [{"type":"index-pattern","id":"c7fbafd0-34a9-11eb-925f-9db57376c4ce","attributes":{"title":".monitoring-es-7-mb-*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2020-12-02T14:34:38.010Z","version":"WzgyNCw3XQ==","namespaces":["default"],"score":0},{"type":"index-pattern","id":"861ea7f0-3a9b-11eb-9b55-45d33507027a","attributes":{"title":"mock_log*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2020-12-10T04:09:09.044Z","version":"WzE3NTgsMTBd","namespaces":["default"],"score":0},{"type":"index-pattern","id":"1a28c950-0f6b-11eb-9512-2d0c0eda237d","attributes":{"title":"gateway_requests*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2021-05-22T11:04:23.811Z","version":"WzkxMTgsNDhd","namespaces":["default"],"score":0},{"type":"index-pattern","id":"1ccce5c0-bb9a-11eb-957b-939add21a246","attributes":{"title":"test-custom*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2021-05-23T07:40:14.747Z","version":"WzkxOTEsNDhd","namespaces":["default"],"score":0}]; const [state, setState] = useState({ - columns: columnsParam||['_source'],//['name', 'address'], - interval: 'auto', - }); - + columns: columnsParam || ["_source"], //['name', 'address'], + interval: "auto", + }); // const [sort, setSort] = useState(null); - const subscriptions = useMemo(()=>{ + const subscriptions = useMemo(() => { const subscriptions = new Subscription(); subscriptions.add( timefilter.getAutoRefreshFetch$().subscribe({ @@ -84,105 +99,126 @@ const Discover = (props)=>{ }) ); return subscriptions; - },[props.indexPattern]); - const setIndexPattern = async (id)=>{ - const IP = await services.indexPatternService.get(id); - subscriptions.unsubscribe(); - props.changeIndexPattern(IP); - setState({ - ...state, - columns: ['_source'], - sort: [], - }) - } - - //const indexPatterns = [{"id":"1ccce5c0-bb9a-11eb-957b-939add21a246","type":"index-pattern","namespaces":["default"],"updated_at":"2021-05-23T07:40:14.747Z","version":"WzkxOTEsNDhd","attributes":{"title":"test-custom*","timeFieldName":"created_at","fields":"[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"address\"}}},{\"count\":0,\"conflictDescriptions\":{\"text\":[\"test-custom1\"],\"long\":[\"test-custom\",\"test-custom8\",\"test-custom9\"]},\"name\":\"age\",\"type\":\"conflict\",\"esTypes\":[\"text\",\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"age.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"age\"}}},{\"count\":0,\"name\":\"created_at\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"email.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"email\"}}},{\"count\":0,\"name\":\"hobbies\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"hobbies.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"hobbies\"}}},{\"count\":0,\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"id\"}}},{\"count\":0,\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"name\"}}}]"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"}}]; - const indexPattern = props.indexPattern; - const indexPatterns = [indexPattern]; - const indexPatternList = props.indexPatternList - const contentCentered = false; //resultState != 'ready'; + }, [props.indexPattern]); + const setIndexPattern = async (id, typ) => { + const IP = await services.indexPatternService.get(id, typ); + subscriptions.unsubscribe(); + props.changeIndexPattern(IP); + setState({ + ...state, + columns: ["_source"], + sort: [], + }); + }; + const onTimeFieldChange = async (id, timeField) => { + const IP = await services.indexPatternService.get(id, "index"); + IP.timeFieldName = timeField; + props.changeIndexPattern(IP); + }; + + //const indexPatterns = [{"id":"1ccce5c0-bb9a-11eb-957b-939add21a246","type":"index-pattern","namespaces":["default"],"updated_at":"2021-05-23T07:40:14.747Z","version":"WzkxOTEsNDhd","attributes":{"title":"test-custom*","timeFieldName":"created_at","fields":"[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"address\"}}},{\"count\":0,\"conflictDescriptions\":{\"text\":[\"test-custom1\"],\"long\":[\"test-custom\",\"test-custom8\",\"test-custom9\"]},\"name\":\"age\",\"type\":\"conflict\",\"esTypes\":[\"text\",\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"age.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"age\"}}},{\"count\":0,\"name\":\"created_at\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"email.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"email\"}}},{\"count\":0,\"name\":\"hobbies\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"hobbies.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"hobbies\"}}},{\"count\":0,\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"id\"}}},{\"count\":0,\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"name\"}}}]"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"}}]; + const indexPattern = props.indexPattern; + const indexPatterns = [indexPattern]; + const indexPatternList = props.indexPatternList; + const contentCentered = false; //resultState != 'ready'; + + indexPatterns.get = (id) => { + return Promise.resolve(indexPatterns.find((ip) => ip.id == id)); + }; + getContext().setIndexPatterns(indexPatterns); - indexPatterns.get = (id)=>{ - return Promise.resolve(indexPatterns.find(ip=>ip.id==id)); - } - getContext().setIndexPatterns(indexPatterns) - const [expandedDoc, setExpandedDoc] = useState(undefined); const scrollableDesktop = useRef(null); - + const columns = state.columns; const updateQuery = useCallback( async (_payload) => { - if(!indexPattern){ - return + if (!indexPattern) { + return; } - setResultState('loading'); - const params = getSearchParams(_payload?.indexPattern || indexPattern, _payload?.interval || state.interval, _payload?.sort); - const res = await fetchESRequest(params, props.selectedCluster.id) - if(!res.hits.hits){ - res.hits.hits=[]; + setResultState("loading"); + const params = getSearchParams( + _payload?.indexPattern || indexPattern, + _payload?.interval || state.interval, + _payload?.sort + ); + const res = await fetchESRequest(params, props.selectedCluster.id); + if (!res.hits.hits) { + res.hits.hits = []; } setSearchRes(res); - const {query} = queryStringManager.getQuery(); - if(query != queryParam){ + const { query } = queryStringManager.getQuery(); + if (query != queryParam) { setQueryParam(query); } // let filters = filterManager.getFilters(); // const tfilter = timefilter.createFilter(indexPattern); // filters = [...filterManager.getFilters(), ...(tfilter ? [tfilter] : [])], - + // console.log(filters) // console.log(res) //console.log(JSON.stringify(params)); - //console.log(getEsQuery(indexPattern)) + // console.log(getEsQuery(indexPattern)); + // console.log(timefilter.createFilter(indexPattern)); }, - [indexPattern, state.interval,] + [indexPattern, state.interval] ); const onChangeInterval = useCallback( (interval) => { if (interval) { //console.log(calculateInterval(interval)) - setState({...state, interval }); + setState({ ...state, interval }); } }, [setState, indexPattern] ); - const [searchRes, setSearchRes] = useState( - {"took":11,"timed_out":false,"_shards":{"total":4,"successful":4,"skipped":3,"failed":0},"hits":{"total":0,"max_score":null,"hits":[]},"aggregations":{"2":{"buckets":[]}}} - ); + const [searchRes, setSearchRes] = useState({ + took: 11, + timed_out: false, + _shards: { total: 4, successful: 4, skipped: 3, failed: 0 }, + hits: { total: 0, max_score: null, hits: [] }, + aggregations: { "2": { buckets: [] } }, + }); - - const [resultState, setResultState] = useState('loading'); - const {histogramData, timeChartProps} = useMemo(()=>{ - if(!searchRes.hits.hits || searchRes.hits.hits.length == 0){ - setResultState('none'); + const [resultState, setResultState] = useState("loading"); + const { histogramData, timeChartProps } = useMemo(() => { + if (!searchRes.hits.hits || searchRes.hits.hits.length == 0) { + setResultState("none"); return {}; } - if(!indexPattern.timeFieldName || !searchRes.aggregations){ - setResultState('ready'); - return {histogramData:null, timeChartProps:null} + if (!indexPattern.timeFieldName || !searchRes.aggregations) { + setResultState("ready"); + return { histogramData: null, timeChartProps: null }; } const buckets = getTimeBuckets(state.interval); const interval = buckets.getInterval(true); const chartTable = { - columns: [{id:"key", name: `${indexPattern?.getTimeField().displayName} per ${interval.description}`},{id:"doc_count", name:"count"}], + columns: [ + { + id: "key", + name: `${indexPattern?.getTimeField().displayName} per ${ + interval.description + }`, + }, + { id: "doc_count", name: "count" }, + ], rows: [], }; let aggregations = searchRes.aggregations; - - aggregations["2"].buckets.forEach((bk)=>{ + + aggregations["2"].buckets.forEach((bk) => { chartTable.rows.push(bk); - }) - + }); + //console.log(interval, moment.duration('1', 'd')) const dimensions = { x: { accessor: 0, format: { - id: 'date', + id: "date", params: { pattern: buckets.getScaledDateFormat(), //pattern: 'YYYY-MM-DD', @@ -190,7 +226,7 @@ const Discover = (props)=>{ }, params: { date: true, - interval: moment.duration(interval.esValue, interval.esUnit), + interval: moment.duration(interval.esValue, interval.esUnit), intervalESValue: interval.esValue, intervalESUnit: interval.esUnit, bounds: buckets.getBounds(), @@ -199,12 +235,12 @@ const Discover = (props)=>{ y: { accessor: 1, format: { - id: 'number', + id: "number", }, - label: 'Count', - } + label: "Count", + }, }; - setResultState('ready'); + setResultState("ready"); const timeChartProps = { timeRange: { from: timefilter.getBounds().min, @@ -217,30 +253,35 @@ const Discover = (props)=>{ // scaled: true, // description: 'day', // scale: undefined, - ...interval + ...interval, + timeFieldName: indexPattern.timeFieldName, }, - } + }; const histogramData = buildPointSeriesData(chartTable, dimensions); - return {histogramData, timeChartProps} - }, [searchRes, indexPattern]) + return { histogramData, timeChartProps }; + }, [searchRes, indexPattern, indexPattern.timeFieldName]); - const setAppState = (newState)=>{ - if(state.columns[0] === '_source' || newState.columns[0] === '_source' || (state.columns.length != newState.columns.length)){ + const setAppState = (newState) => { + if ( + state.columns[0] === "_source" || + newState.columns[0] === "_source" || + state.columns.length != newState.columns.length + ) { setColumnsParam(newState.columns); } - setState(newState) - } - + setState(newState); + }; + const { onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useMemo( () => getStateColumnActions({ indexPattern, - indexPatterns:[indexPattern], + indexPatterns: [indexPattern], setAppState: setAppState, state, - useNewFieldsApi:false, + useNewFieldsApi: false, }), - [indexPattern,state] + [indexPattern, state] ); const collapseIcon = useRef(null); @@ -250,18 +291,19 @@ const Discover = (props)=>{ }, [state]); const hideChart = useMemo(() => state.hideChart, [state]); const [isSidebarClosed, setIsSidebarClosed] = useState(false); - const useNewFieldsApi = false - + const useNewFieldsApi = false; + const onSort = useCallback( (nsort) => { - setState({...state, sort: nsort}) - updateQuery({sort: nsort}); - },[state, indexPattern] + setState({ ...state, sort: nsort }); + updateQuery({ sort: nsort }); + }, + [state, indexPattern] ); const onAddFilter = useCallback( (field, values, operation) => { - const fieldName = typeof field === 'string' ? field : field.name; + const fieldName = typeof field === "string" ? field : field.name; const newFilters = generateFilters( filterManager, field, @@ -270,7 +312,7 @@ const Discover = (props)=>{ String(indexPattern.id) ); filterManager.addFilters(newFilters); - updateQuery() + updateQuery(); }, [indexPattern] ); @@ -281,96 +323,110 @@ const Discover = (props)=>{ timefilter.setTime({ from: moment(ranges.from).toISOString(), to: moment(ranges.to).toISOString(), - mode: 'absolute', + mode: "absolute", }); }, [timefilter] ); - const rows = searchRes.hits.hits; - + const rows = searchRes.hits.hits; - const opts = { - savedSearch:{}, - timefield: indexPattern?.getTimeField()?.displayName, - chartAggConfigs: {}, - }; - const fieldCounts = {}; - for (const hit of rows) { - for (const key of Object.keys(indexPattern.flattenHit(hit, true))) { - fieldCounts[key] = (fieldCounts[key] || 0) + 1; - } + const opts = { + savedSearch: {}, + timefield: indexPattern?.getTimeField()?.displayName, + chartAggConfigs: {}, + }; + const fieldCounts = {}; + for (const hit of rows) { + for (const key of Object.keys(indexPattern.flattenHit(hit, true))) { + fieldCounts[key] = (fieldCounts[key] || 0) + 1; } - const hits = searchRes.hits.total?.value || searchRes.hits.total; - const resetQuery = ()=>{}; - const showDatePicker = indexPattern.timeFieldName != ""; + } + const hits = searchRes.hits.total?.value || searchRes.hits.total; + const resetQuery = () => {}; + const showDatePicker = indexPattern.timeFieldName != ""; - const saveDocument = useCallback(async ({_index, _id, _type, _source, is_new})=>{ - const {http} = getContext(); - const res = await http.put(`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, { - prependBasePath: false, - query: { - _type, - is_new, - }, - body: JSON.stringify(_source), - }); - if(res.error){ - message.error(res.error) - return res - } - message.success('saved successfully'); - updateQuery() - return res; - },[props.selectedCluster, updateQuery]) - - const deleteDocument = useCallback(async ({_index, _id, _type})=>{ - const {http} = getContext(); - const res = await http.delete(`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, { - prependBasePath: false, - query: { - _type, + const saveDocument = useCallback( + async ({ _index, _id, _type, _source, is_new }) => { + const { http } = getContext(); + const res = await http.put( + `/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, + { + prependBasePath: false, + query: { + _type, + is_new, + }, + body: JSON.stringify(_source), } - }); - if(res.error){ - message.error(res.error) - return res - } - message.success('deleted successfully'); - updateQuery() - return res - },[props.selectedCluster, updateQuery]) - - return ( - - + ); + if (res.error) { + message.error(res.error); + return res; + } + message.success("saved successfully"); + updateQuery(); + return res; + }, + [props.selectedCluster, updateQuery] + ); - - - {indexPattern && + const deleteDocument = useCallback( + async ({ _index, _id, _type }) => { + const { http } = getContext(); + const res = await http.delete( + `/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, + { + prependBasePath: false, + query: { + _type, + }, + } + ); + if (res.error) { + message.error(res.error); + return res; + } + message.success("deleted successfully"); + updateQuery(); + return res; + }, + [props.selectedCluster, updateQuery] + ); + + const [settingsVisible, setSettingsVisible] = React.useState(false); + + return ( + + + + + + {indexPattern && ( + { isClosed={isSidebarClosed} //unmappedFieldsConfig={unmappedFieldsConfig} //useNewFieldsApi={useNewFieldsApi} + indices={props.indices} /> - } - - - setIsSidebarClosed(!isSidebarClosed)} - data-test-subj="collapseSideBarButton" - aria-controls="discover-sidebar" - aria-expanded={isSidebarClosed ? 'false' : 'true'} - aria-label={'Toggle sidebar'} - buttonRef={collapseIcon} - /> - - - {resultState === 'none' && ( - - )} - {resultState !== 'none' && ( - + + )} + + + { + setIsSidebarClosed(!isSidebarClosed); + setTimeout(() => { + window.dispatchEvent(new Event("resize")); + }, 50); + }} + data-test-subj="collapseSideBarButton" + aria-controls="discover-sidebar" + aria-expanded={isSidebarClosed ? "false" : "true"} + aria-label={"Toggle sidebar"} + buttonRef={collapseIcon} + /> + + + {resultState === "none" && ( + + )} + {resultState !== "none" && ( + -
+ {/* todo add settings icon */} + {props.timeFields.length > 0 && ( +
{ + setSettingsVisible(!settingsVisible); + }} + > + +
+ )} + {settingsVisible ? ( + { + setSettingsVisible(visible); + }} + onTimeFieldChange={onTimeFieldChange} + /> + ) : null} +
{/* {resultState === 'loading' && } */} - { ( + { { gutterSize="none" responsive={false} > - {hits > 0 && - - 0 && ( + + {/* - 0 ? hits : 0} - showResetButton={!!(opts.savedSearch && opts.savedSearch.id)} - onResetQuery={resetQuery} - /> - - - - {!hideChart && ( - - + 0 ? hits : 0} + showResetButton={ + !!(opts.savedSearch && opts.savedSearch.id) + } + onResetQuery={resetQuery} /> - )} - - } - {!hideChart && opts.timefield && ( - -
*/} + +
- {opts.chartAggConfigs && histogramData && rows.length !== 0 && ( -
- 0 ? hits : 0} + dateFormat={"YYYY-MM-DD H:mm"} + {...timeChartProps} + /> +
+ {props.timeFields.length > 0 && + indexPattern.timeFieldName == "" && ( +
+
+ Click the button of right corner to select a + time field +
+
)} + + )} + {!hideChart && opts.timefield && ( + +
+ {opts.chartAggConfigs && + histogramData && + rows.length !== 0 && ( +
+ +
+ )}
@@ -491,131 +609,187 @@ const Discover = (props)=>{ ref={scrollableDesktop} tabIndex={-1} > -

- Documents +

+ Documents

- - {(rows && rows.length > 0) ? ( + + {rows && rows.length > 0 ? (
- {/* */} - + document={{ saveDocument, deleteDocument }} + hits={rows} + /> - ):null} + ) : null} - )} + } - )} - - - + + )} + + + ); -} +}; -const DiscoverUI = (props)=>{ - const [viewID, setViewID] = useQueryParam('viewID', StringParam); - const [queryParam, setQueryParam] = useQueryParam('query', StringParam); +const DiscoverUI = (props) => { + const [viewID, setViewID] = useQueryParam("viewID", StringParam); + const [index, setIndex] = useQueryParam("index", StringParam); + // const [type, setType] = useQueryParam('type', StringParam); + const [queryParam, setQueryParam] = useQueryParam("query", StringParam); const [state, setState] = useState({ + indices: [], + timeFields: [], }); - useMemo(()=>{ - const {http} = getContext(); - http.getServerBasePath = ()=>{ - return `${ESPrefix}/`+ props.selectedCluster.id; - } - }, [props.selectedCluster]) - useEffect(()=>{ - if(queryParam){ + useMemo(() => { + const { http } = getContext(); + http.getServerBasePath = () => { + return `${ESPrefix}/` + props.selectedCluster.id; + }; + }, [props.selectedCluster]); + + const getTimeFields = (IP) => { + const timeFields = []; + IP.fields.forEach((field) => { + if (field.spec.type == "date") { + timeFields.push(field.displayName); + } + }); + return timeFields; + }; + useEffect(() => { + if (queryParam) { queryStringManager.setQuery({ query: queryParam, - language: 'kuery' + language: "kuery", }); } - const {http} = getContext(); - const initialFetch = async ()=>{ + const { http } = getContext(); + const initialFetch = async () => { + const indices = await http.fetch( + http.getServerBasePath() + "/_cat/indices" + ); + const indexNames = Object.keys(indices); //.filter(key=>!key.startsWith(".")); const ils = await services.savedObjects.savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title'], - search:'', - searchFields: ['title'], + type: "view", + fields: ["title"], + search: "", + searchFields: ["title"], perPage: 100, }); - if(ils.length === 0){ - props.history.push('/data/views/'); - return + + if (ils.length === 0 && indexNames.length === 0) { + props.history.push("/data/views/"); + return; } - const defaultIndex = viewID || await http.fetch(http.getServerBasePath()+'/setting/defaultIndex') - const targetIndex = ils.filter(il=>il.id == defaultIndex); - const defaultIP = await services.indexPatternService.get(targetIndex.length > 0 ? defaultIndex : ils[0]?.id) + let defaultIndex = viewID; + let defaultIP = null; + if (indexNames.includes(index)) { + defaultIP = await services.indexPatternService.get( + index, + "index", + props.selectedCluster.id + ); + } else { + if (ils.length > 0) { + defaultIndex = + viewID || + (await http.fetch( + http.getServerBasePath() + "/setting/defaultIndex" + )); + let targetIndex = ils.filter((il) => il.id == defaultIndex); + defaultIP = await services.indexPatternService.get( + targetIndex.length > 0 ? defaultIndex : ils[0]?.id + ); + if (targetIndex.length === 0) { + setViewID(ils[0]?.id); + } + } else { + defaultIP = await services.indexPatternService.get( + indexNames[0], + "index" + ); + setIndex(indexNames[0]); + } + } + const timeFields = getTimeFields(defaultIP); setState({ indexPatternList: ils, - indexPattern: defaultIP + indexPattern: defaultIP, + indices: indexNames, + timeFields, }); - if(!viewID){ - setViewID(defaultIP.id); - } - } + // if (!viewID) { + // setViewID(defaultIP.id); + // } + }; initialFetch(); // return ()=>{ // queryStringManager.setQuery(''); // } - },[props.selectedCluster]); + }, [props.selectedCluster]); - function changeIndexPattern(indexPattern){ - setState({ - ...state, - indexPattern, - }); - setViewID(indexPattern.id); - } - - return ( - state.indexPatternList && state.indexPatternList.length > 0 ? - :
- ) -} + const changeIndexPattern = React.useCallback( + (indexPattern) => { + const timeFields = getTimeFields(indexPattern); + setState({ + ...state, + indexPattern, + timeFields, + }); + if (indexPattern.type == "index") { + setIndex(indexPattern.id); + setViewID(undefined); + } else { + setViewID(indexPattern.id); + setIndex(undefined); + } + }, + [state] + ); -const DiscoverContainer = (props)=>{ - if(props.selectedCluster.id == ""){ + return (state.indexPatternList && state.indexPatternList.length > 0) || + state.indices.length > 0 ? ( + + ) : ( +
+ +
+ ); +}; + +const DiscoverContainer = (props) => { + if (props.selectedCluster.id == "") { return null; } - return - - -} + return ( + + + + ); +}; -export default connect(({ - global -})=>({ +export default connect(({ global }) => ({ selectedCluster: global.selectedCluster, -}))(DiscoverContainer) \ No newline at end of file +}))(DiscoverContainer); diff --git a/web/src/pages/DataManagement/Index.js b/web/src/pages/DataManagement/Index.js index 0e0f5f10..32d63b5c 100644 --- a/web/src/pages/DataManagement/Index.js +++ b/web/src/pages/DataManagement/Index.js @@ -135,12 +135,7 @@ class Index extends PureComponent { title: '索引名称', dataIndex: 'index', render: (text, record) => ( - { - this.setState({ - editingIndex: record, - drawerVisible: true, - }); - }}>{text} + {text} ) }, { @@ -162,8 +157,13 @@ class Index extends PureComponent { title: '操作', render: (text, record) => ( - {/* this.handleUpdateModalVisible(true, record)}>设置 - */} + { + this.setState({ + editingIndex: record, + drawerVisible: true, + }); + }}>设置 + this.handleDeleteClick(record.index)}> 删除 diff --git a/web/src/pages/DataManagement/SettingContent.jsx b/web/src/pages/DataManagement/SettingContent.jsx new file mode 100644 index 00000000..433daa93 --- /dev/null +++ b/web/src/pages/DataManagement/SettingContent.jsx @@ -0,0 +1,98 @@ +import { EuiToolTip, EuiSelect, EuiIconTip } from "@elastic/eui"; +import { useOnClickOutside } from "@/lib/hooks/use_click_outsize"; +import { useEffect, useState } from "react"; + +export const SettingContent = ({ + onVisibleChange, + bucketInterval = {}, + options = [], + onChangeInterval, + stateInterval, + indexPattern, + timeFields, + onTimeFieldChange, +}) => { + const [interval, setInterval] = useState(stateInterval); + useEffect(() => { + setInterval(stateInterval); + }, [stateInterval]); + + const [settingRef] = useOnClickOutside(() => { + if (typeof onVisibleChange == "function") { + onVisibleChange(false); + } + }); + + const handleIntervalChange = (e) => { + setInterval(e.target.value); + onChangeInterval(e.target.value); + }; + + return ( +
+
+
+ Time Field +
+ { + return { + value: tf, + text: tf, + }; + })} + onChange={(e) => { + onTimeFieldChange(indexPattern.id, e.target.value); + }} + /> +
+
+ {indexPattern.timeFieldName ? ( +
+ Time Interval +
+ val !== "custom") + .map(({ display, val }) => { + return { + text: display, + value: val, + label: display, + }; + })} + value={interval} + onChange={handleIntervalChange} + append={ + bucketInterval.scaled ? ( + 1 + ? "buckets that are too large" + : "too many buckets" + } to show in the selected time range, so it has been scaled to ${ + bucketInterval.description + }.`} + color="warning" + size="s" + type="alert" + /> + ) : ( + undefined + ) + } + /> +
+
+ ) : null} +
+
+ ); +}; diff --git a/web/src/pages/DataManagement/discover.scss b/web/src/pages/DataManagement/discover.scss index 691e54bd..221aca2a 100644 --- a/web/src/pages/DataManagement/discover.scss +++ b/web/src/pages/DataManagement/discover.scss @@ -21,17 +21,18 @@ .dscPageContent__wrapper { padding: 0 5 5 0; overflow: hidden; // Ensures horizontal scroll of table - box-shadow: 0 2px 2px -1px rgb(152 162 179 / 30%), 0 1px 5px -2px rgb(152 162 179 / 30%); + box-shadow: 0 2px 2px -1px rgb(152 162 179 / 30%), + 0 1px 5px -2px rgb(152 162 179 / 30%); // border: 1px solid #D3DAE6; - border-radius: 4px; - flex-grow: 1; + border-radius: 4px; + flex-grow: 1; } .dscPageContent { //border: $euiBorderThin; // text-align: center; box-shadow: none !important; - .euiDataGrid--fullScreen{ + .euiDataGrid--fullScreen { z-index: 801; } position: relative; @@ -57,7 +58,7 @@ // SASSTODO: the visualizing component should have an option or a modifier .series > rect { - fill-opacity: .5; + fill-opacity: 0.5; stroke-width: 1; } } @@ -75,23 +76,24 @@ // padding: 5 5 0 5; // } .euiDataGrid--headerUnderline .euiDataGridHeaderCell { - border-bottom: 1px solid #D3DAE6 !important; + border-bottom: 1px solid #d3dae6 !important; border-top: none !important; } -.dscSidebar{ - .euiButton--text, .euiButton--text:hover{ +.dscSidebar { + .euiButton--text, + .euiButton--text:hover { background-color: #fff; } } -.sidebar-list{ +.sidebar-list { max-width: 200px; } .euiSuperUpdateButton.euiButton--success { - background-color: #017D73 !important; - border-color: #017D73 !important; - color: #FFF !important; + background-color: #017d73 !important; + border-color: #017d73 !important; + color: #fff !important; border-radius: 0 !important; } @@ -109,6 +111,64 @@ background-color: transparent; } -.kbnQueryBar__wrap{ +.kbnQueryBar__wrap { box-shadow: none; -} \ No newline at end of file +} + +.dscSetting { + position: absolute; + right: 0px; + top: 0; + &.setting-icon { + cursor: pointer; + padding: 0 4px; + &:hover { + background-color: #d3dae6; + } + } + &.setting-content { + background-color: #fff; + box-shadow: 0 2px 8px rgb(0 0 0 / 15%); + width: 300px; + max-height: 200px; + overflow-y: scroll; + z-index: 2; + .setting-wrapper { + padding: 10px; + } + .setting-row { + display: flex; + align-items: center; + margin-bottom: 10px; + &:last-child { + margin-bottom: 0; + } + .label { + // font-size: 12px; + width: 90px; + flex: none; + text-align: right; + margin-right: 5px; + } + } + } +} + +.fake-chart { + position: relative; + .fake-mask { + position: absolute; + left: 0; + top: 0; + bottom: 0; + z-index: 1; + right: 0; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + font-size: 24px; + font-weight: 600; + background-color: rgba($color: #000000, $alpha: 0.15); + } +} diff --git a/web/src/pages/DevTool/Console.tsx b/web/src/pages/DevTool/Console.tsx index c4d08384..72b423c2 100644 --- a/web/src/pages/DevTool/Console.tsx +++ b/web/src/pages/DevTool/Console.tsx @@ -1,25 +1,40 @@ -import Console from '../../components/kibana/console/components/Console'; -import {connect} from 'dva'; -import {Button, Icon, Menu, Dropdown, } from 'antd'; -import Tabs from '@/components/infini/tabs'; -import {DraggableTabs} from '@/components/infini/tabs/DraggableTabs'; -import {useState, useReducer, useCallback, useEffect, useMemo, useRef, useLayoutEffect} from 'react'; -import {useLocalStorage} from '@/lib/hooks/storage'; -import {setClusterID} from '../../components/kibana/console/modules/mappings/mappings'; -import {TabTitle} from './console_tab_title'; -import '@/assets/utility.scss'; +import Console from "../../components/kibana/console/components/Console"; +import { connect } from "dva"; +import { Button, Icon, Menu, Dropdown } from "antd"; +import Tabs from "@/components/infini/tabs"; +import { DraggableTabs } from "@/components/infini/tabs/DraggableTabs"; +import { + useState, + useReducer, + useCallback, + useEffect, + useMemo, + useRef, + useLayoutEffect, +} from "react"; +import { useLocalStorage } from "@/lib/hooks/storage"; +import { setClusterID } from "../../components/kibana/console/modules/mappings/mappings"; +import { TabTitle } from "./console_tab_title"; +import "@/assets/utility.scss"; import { Resizable } from "re-resizable"; -import {ResizeBar} from '@/components/infini/resize_bar'; -import NewTabMenu from './NewTabMenu'; +import { ResizeBar } from "@/components/infini/resize_bar"; +import NewTabMenu from "./NewTabMenu"; + +import maximizeSvg from "@/assets/window-maximize.svg"; +import restoreSvg from "@/assets/window-restore.svg"; + +const MaximizeIcon = (props = {}) => { + return ; +}; +const RestoreIcon = (props = {}) => { + return ; +}; const { TabPane } = Tabs; -const TabConsole = (props:any)=>{ - return ( - - ) -} - +const TabConsole = (props: any) => { + return ; +}; // export default connect(({ // global @@ -29,331 +44,381 @@ const TabConsole = (props:any)=>{ const addTab = (state: any, action: any) => { const { panes } = state; - const {cluster} = action.payload; + const { cluster } = action.payload; const activeKey = `${cluster.id}:${new Date().valueOf()}`; - panes.push({ key: activeKey, cluster_id: cluster.id, title: cluster.name}); + panes.push({ key: activeKey, cluster_id: cluster.id, title: cluster.name }); return { ...state, panes, activeKey, - } -} -const removeTab = (state: any, action: any) =>{ + }; +}; +const removeTab = (state: any, action: any) => { const { activeKey, panes } = state; - const {targetKey} = action.payload; - const newPanes = panes.filter(pane => pane.key !== targetKey); + const { targetKey } = action.payload; + + const newPanes = panes.filter((pane) => pane.key !== targetKey); return { ...state, panes: newPanes, - activeKey: panes[0]?.key, - } -} + activeKey: activeKey == targetKey ? newPanes[0]?.key : activeKey, + }; +}; const consoleTabReducer = (state: any, action: any) => { - const {type, payload} = action; + const { type, payload } = action; let newState = state; - switch(type){ - case 'add': + switch (type) { + case "add": newState = addTab(state, action); break; - case 'remove': - newState = removeTab(state, action); + case "remove": + newState = removeTab(state, action); break; - case 'change': + case "change": newState = { ...state, activeKey: payload.activeKey, - } + }; break; - case 'saveTitle': - const {key, title} = action.payload; - const newPanes = state.panes.map((pane: any)=>{ - if(pane.key == key){ + case "saveTitle": + const { key, title } = action.payload; + const newPanes = state.panes.map((pane: any) => { + if (pane.key == key) { return { ...pane, title, - } + }; } return pane; }); newState = { ...state, panes: newPanes, - } + }; break; - case 'saveContent': - const panes = state.panes.map((pane)=>{ - if(pane.key == state.activeKey){ + case "saveContent": + const panes = state.panes.map((pane) => { + if (pane.key == state.activeKey) { return { ...pane, content: action.payload.content, - } + }; } return pane; }); - newState = ({ + newState = { ...state, panes, - }); + }; break; - case 'saveOrder': - newState = ({ + case "saveOrder": + newState = { ...state, order: action.payload.order, - }); + }; default: } // setLocalState(newState); return newState; -} +}; -function calcHeightToPX(height: string){ - const intHeight = parseInt(height) - if(height.endsWith('vh')){ - return Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) * intHeight / 100; - }else{ +function calcHeightToPX(height: string) { + const intHeight = parseInt(height); + if (height.endsWith("vh")) { + return ( + (Math.max( + document.documentElement.clientHeight || 0, + window.innerHeight || 0 + ) * + intHeight) / + 100 + ); + } else { return intHeight; } } -export const ConsoleUI = ({selectedCluster, - clusterList, - clusterStatus, - minimize=false, +export const ConsoleUI = ({ + selectedCluster, + clusterList, + clusterStatus, + minimize = false, onMinimizeClick, - resizeable=false, - height='50vh' -}: any)=>{ - const clusterMap = useMemo(()=>{ + resizeable = false, + height = "50vh", +}: any) => { + const clusterMap = useMemo(() => { let cm = {}; - if(!clusterStatus){ + if (!clusterStatus) { return cm; } - (clusterList || []).map((cluster: any)=>{ + (clusterList || []).map((cluster: any) => { cluster.status = clusterStatus[cluster.id]?.health?.status; - if(!clusterStatus[cluster.id]?.available){ - cluster.status = 'unavailable'; - } + if (!clusterStatus[cluster.id]?.available) { + cluster.status = "unavailable"; + } cm[cluster.id] = cluster; }); return cm; - }, [clusterList, clusterStatus]) - const initialDefaultState = ()=>{ + }, [clusterList, clusterStatus]); + const initialDefaultState = () => { let defaultCluster = selectedCluster; - if(!defaultCluster.id){ - defaultCluster = clusterList[0] ; + if (!defaultCluster.id) { + defaultCluster = clusterList[0]; } - const defaultActiveKey = `${defaultCluster.id || ''}:${new Date().valueOf()}`; - const defaultState = defaultCluster? { - panes:[{ - key: defaultActiveKey, cluster_id: defaultCluster.id, title: defaultCluster.name - }], - activeKey: defaultActiveKey, - }: {panes:[],activeKey:''}; - return defaultState - } - - const [localState, setLocalState, removeLocalState] = useLocalStorage("console:state", initialDefaultState, { - encode: JSON.stringify, - decode: JSON.parse, - }) - const [tabState, dispatch] = useReducer(consoleTabReducer, localState) - - useEffect(()=>{ - if(tabState.panes.length == 0){ - removeLocalState() - return - } - setLocalState(tabState) - }, [tabState]) - - const saveEditorContent = useCallback((content)=>{ - dispatch({ - type: 'saveContent', - payload: { - content - } - }) - }, [dispatch]) - - const onChange = (activeKey:string) => { - dispatch({ - type: 'change', - payload: { - activeKey - } - }) + const defaultActiveKey = `${defaultCluster.id || + ""}:${new Date().valueOf()}`; + const defaultState = defaultCluster + ? { + panes: [ + { + key: defaultActiveKey, + cluster_id: defaultCluster.id, + title: defaultCluster.name, + }, + ], + activeKey: defaultActiveKey, + } + : { panes: [], activeKey: "" }; + return defaultState; }; - const onEdit = (targetKey: string | React.MouseEvent, action:string) => { + const [localState, setLocalState, removeLocalState] = useLocalStorage( + "console:state", + initialDefaultState, + { + encode: JSON.stringify, + decode: JSON.parse, + } + ); + const [tabState, dispatch] = useReducer(consoleTabReducer, localState); + + useEffect(() => { + if (tabState.panes.length == 0) { + removeLocalState(); + return; + } + setLocalState(tabState); + }, [tabState]); + + const saveEditorContent = useCallback( + (content) => { + dispatch({ + type: "saveContent", + payload: { + content, + }, + }); + }, + [dispatch] + ); + + const onChange = (activeKey: string) => { + dispatch({ + type: "change", + payload: { + activeKey, + }, + }); + }; + + const onEdit = ( + targetKey: string | React.MouseEvent, + action: string + ) => { dispatch({ type: action, payload: { targetKey, - } - }) + }, + }); }; - const newTabClick = useCallback((param: any)=>{ - const cluster = clusterList.find(item=>item.id == param.id); - if(!cluster){ - console.log('cluster not found') - return; - } - dispatch({ - type:'add', - payload: { - cluster, + const newTabClick = useCallback( + (param: any) => { + const cluster = clusterList.find((item) => item.id == param.id); + if (!cluster) { + console.log("cluster not found"); + return; } - }) - },[clusterList]) - - const menu = ( - // - // {(clusterList||[]).map((cluster:any)=>{ - // return {cluster.name} - // })} - // - + dispatch({ + type: "add", + payload: { + cluster, + }, + }); + }, + [clusterList] ); - + + // const menu = ( + // // + // // {(clusterList||[]).map((cluster:any)=>{ + // // return {cluster.name} + // // })} + // // + // + // ); + const rootRef = useRef(null); const [isFullscreen, setIsFullscreen] = useState(false); - const fullscreenClick = ()=>{ - if(rootRef.current != null){ - if(!isFullscreen){ + const fullscreenClick = () => { + if (rootRef.current != null) { + if (!isFullscreen) { rootRef.current.className = rootRef.current.className + " fullscreen"; // rootRef.current.style.overflow = 'scroll'; - }else{ - rootRef.current.className = rootRef.current.className.replace(' fullscreen', ''); + } else { + rootRef.current.className = rootRef.current.className.replace( + " fullscreen", + "" + ); } } - setEditorHeight(rootRef.current.clientHeight) - setIsFullscreen(!isFullscreen) - } + setEditorHeight(rootRef.current.clientHeight); + setIsFullscreen(!isFullscreen); + }; - const tabBarExtra ={ - left:
- {minimize? :null} -
, - right:( -
- {isFullscreen? - : - - } + const tabBarExtra = { + left: ( +
+ {minimize ? : null}
- ), - append:( -
- - - -
- ) -}; + ), + right: ( + <> +
+ {minimize ? : null} +
+
+ {isFullscreen ? : } +
+ + ), + append: ( + +
+ +
+
+ ), + }; - setClusterID(tabState.activeKey?.split(':')[0]); - const panes = tabState.panes.filter((pane: any)=>{ - return typeof clusterMap[pane.cluster_id] != 'undefined'; - }) + setClusterID(tabState.activeKey?.split(":")[0]); + const panes = tabState.panes.filter((pane: any) => { + return typeof clusterMap[pane.cluster_id] != "undefined"; + }); - const saveTitle = (key: string, title: string)=>{ + const saveTitle = (key: string, title: string) => { dispatch({ - type:'saveTitle', + type: "saveTitle", payload: { key, title, - } - }) - } - const [editorHeight, setEditorHeight] = useState(calcHeightToPX(height)) - const onResize = (_env, _dir, refToElement, delta)=>{ + }, + }); + }; + const [editorHeight, setEditorHeight] = useState(calcHeightToPX(height)); + const onResize = (_env, _dir, refToElement, delta) => { // console.log(refToElement.offsetHeight, delta) - setEditorHeight(refToElement.clientHeight) - } + setEditorHeight(refToElement.clientHeight); + }; - const disableWindowScroll = ()=>{ - document.body.style.overflow = 'hidden' - } + const disableWindowScroll = () => { + document.body.style.overflow = "hidden"; + }; - const enableWindowScroll = ()=>{ - document.body.style.overflow = ''; - } - const onTabNodeMoved=(newOrder:string[])=>{ + const enableWindowScroll = () => { + document.body.style.overflow = ""; + }; + const onTabNodeMoved = (newOrder: string[]) => { dispatch({ - type:'saveOrder', + type: "saveOrder", payload: { - order: newOrder, - } - }) - } - - + order: newOrder, + }, + }); + }; + return ( }} - onResize={onResize} - enable={{ - top: resizeable, - right: false, - bottom: false, - left: false, - topRight: false, - bottomRight: false, - bottomLeft: false, - topLeft: false, - }}> -
- }} + onResize={onResize} + enable={{ + top: resizeable, + right: false, + bottom: false, + left: false, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false, + }} + > +
- {panes.map(pane => ( - {saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}> - - {/* {pane.content} */} - - ))} - -
- + + {panes.map((pane) => ( + { + saveTitle(pane.key, title); + }} + /> + } + key={pane.key} + closable={pane.closable} + > + + {/* {pane.content} */} + + ))} + +
+
); -} +}; -export default connect(({ - global -})=>({ +export default connect(({ global }) => ({ selectedCluster: global.selectedCluster, clusterList: global.clusterList, clusterStatus: global.clusterStatus, - height: window.innerHeight - 75 + 'px', -}))(ConsoleUI); \ No newline at end of file + height: window.innerHeight - 75 + "px", +}))(ConsoleUI); diff --git a/web/src/pages/DevTool/NewTabMenu.jsx b/web/src/pages/DevTool/NewTabMenu.jsx index 43511351..b017a8dc 100644 --- a/web/src/pages/DevTool/NewTabMenu.jsx +++ b/web/src/pages/DevTool/NewTabMenu.jsx @@ -22,11 +22,16 @@ class NewTabMenu extends React.Component{ initialLoad: true, dataSource: [...props.data], dataSourceKey: 1, + selectedIndex: -1, + overlayVisible: false, } } componentDidMount(){ + } + + handleInfiniteOnLoad = (current) => { let {size } = this.props; let targetLength = current * size; @@ -54,17 +59,56 @@ class NewTabMenu extends React.Component{ dataSource: newData, data: newData, hasMore: newData.length > this.props.size, + selectedIndex: -1, }) } + selectOffset = (offset)=> { + let {selectedIndex, data} = this.state; + const len = data.length; + selectedIndex = (selectedIndex + offset + len) % len; + // const item = data[selectedIndex]; + this.setState({ + selectedIndex, + }) + } + + onKeyDown = (e) => { + const { which } = e; + // e.preventDefault(); + + switch (which) { + case 38: + this.selectOffset(-1); + e.preventDefault(); + e.stopPropagation() + break; + case 40: + this.selectOffset(1); + e.stopPropagation() + break; + case 13: + const {data, selectedIndex} = this.state; + if(selectedIndex > -1){ + this.handleItemClick(data[selectedIndex]); + this.setState({ overlayVisible: false }) + } + break; + } + } + render(){ const {clusterStatus} = this.props; - return (
-
+ const menu = (
+
{this.searchInputRef.focus()}} + tabIndex="0" onKeyDown={this.onKeyDown}>
- + {this.searchInputRef= ref;}} + onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} />
{(!this.state.data || !this.state.data.length)&&
匹配不到集群(匹配规则为前缀匹配)
} - {(this.state.data || []).map((item)=>{ + {(this.state.data || []).map((item, idx)=>{ const cstatus = clusterStatus ? clusterStatus[item.id] : null; return { this.handleItemClick(item) }} @@ -93,6 +138,17 @@ class NewTabMenu extends React.Component{
)}
); + return ( +
+ { + this.setState({ overlayVisible: flag }); + }}> + {this.props.children} + +
+ ) } } diff --git a/web/src/pages/DevTool/console_tab_title.scss b/web/src/pages/DevTool/console_tab_title.scss index 9d20d107..398ddfc9 100644 --- a/web/src/pages/DevTool/console_tab_title.scss +++ b/web/src/pages/DevTool/console_tab_title.scss @@ -1,15 +1,28 @@ .tab-title{ display: inline-block; + height: 30px; + overflow: hidden; .input-eidtor{ border-radius: 0; border:none; - border-bottom: 1px solid #ccc; + // border-bottom: 1px solid #ccc; border-right: none; padding-left: 1em; + color:rgba(0, 0, 0, 0.65); + height: 28px; + line-height: 28px; + background-color: #939ea0; + color: #fff; &:focus{ outline: none; } } + .icon-cont{ + overflow: hidden; + height: 100%; + display: flex; + align-items: center; + } } #console{ diff --git a/web/src/pages/DevTool/console_tab_title.tsx b/web/src/pages/DevTool/console_tab_title.tsx index 99121e07..788de017 100644 --- a/web/src/pages/DevTool/console_tab_title.tsx +++ b/web/src/pages/DevTool/console_tab_title.tsx @@ -26,16 +26,26 @@ export const TabTitle = ({title, onTitleChange}: TabTitleProps)=>{ } },[editable]) const inputRef = useRef(null); + const onKeyDown = (e: any)=>{ + const { which } = e; + + switch (which) { + case 13: + e.target.blur(); + break; + } + } + return (
{ setEditable(true) }}> - {editable ? { setEditable(false) }} onChange={onValueChange}/>: -
{value}
} +
{value}
}
) }