modify discover and console

This commit is contained in:
liugq 2021-11-11 15:45:35 +08:00
parent 8685936eb1
commit 354bcd87bb
42 changed files with 2300 additions and 1198 deletions

View File

@ -5,7 +5,7 @@ import (
log "github.com/cihub/seelog" log "github.com/cihub/seelog"
httprouter "infini.sh/framework/core/api/router" httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic" "infini.sh/framework/core/elastic"
"infini.sh/framework/core/metrics" "infini.sh/framework/core/event"
"infini.sh/framework/core/orm" "infini.sh/framework/core/orm"
"infini.sh/framework/core/util" "infini.sh/framework/core/util"
"net/http" "net/http"
@ -77,7 +77,7 @@ func (handler APIHandler) getLatestClusterMonitorData(clusterID interface{}) (ut
] ]
}` }`
queryDSL := fmt.Sprintf(queryDSLTpl, clusterID) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -87,7 +87,7 @@ func main() {
}, func() { }, func() {
orm.RegisterSchemaWithIndexName(model.Dict{}, "dict") orm.RegisterSchemaWithIndexName(model.Dict{}, "dict")
orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex") orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex")
orm.RegisterSchemaWithIndexName(elastic.IndexPattern{}, "view") orm.RegisterSchemaWithIndexName(elastic.View{}, "view")
orm.RegisterSchemaWithIndexName(alerting.Config{}, "alerting-config") orm.RegisterSchemaWithIndexName(alerting.Config{}, "alerting-config")
orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts") orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts")
orm.RegisterSchemaWithIndexName(alerting.AlertingHistory{}, "alerting-alert-history") orm.RegisterSchemaWithIndexName(alerting.AlertingHistory{}, "alerting-alert-history")

View File

@ -33,19 +33,10 @@ export default [
// { path: '/', redirect: '/' }, // { path: '/', redirect: '/' },
// ], // ],
// }, // },
{
path: '/cluster/overview_new',
name: 'overview',
component: './Cluster/NewOverview',
// hideInMenu: true,
routes:[
{ path: '/', redirect: '/' },
],
},
{ {
path: '/cluster/overview', path: '/cluster/overview',
name: 'overview', name: 'overview',
component: './Cluster/Overview', component: './Cluster/NewOverview',
// hideInMenu: true, // hideInMenu: true,
routes:[ routes:[
{ path: '/', redirect: '/' }, { path: '/', redirect: '/' },

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1 @@
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M21,2H3A1,1,0,0,0,2,3V21a1,1,0,0,0,1,1H21a1,1,0,0,0,1-1V3A1,1,0,0,0,21,2ZM20,20H4V10H20ZM20,8H4V4H20Z"/></svg>

After

Width:  |  Height:  |  Size: 206 B

View File

@ -0,0 +1,4 @@
<svg width="512px" height="512px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path fill="var(--ci-primary-color, currentColor)" d="M352,153H40.247a24.028,24.028,0,0,0-24,24V458a24.028,24.028,0,0,0,24,24H352a24.028,24.028,0,0,0,24-24V177A24.028,24.028,0,0,0,352,153Zm-8,32v45.22H48.247V185ZM48.247,450V262.22H344V450Z" class="ci-primary"/>
<path fill="var(--ci-primary-color, currentColor)" d="M472,32H152a24.028,24.028,0,0,0-24,24v65h32V64H464V339.143H408v32h64a24.028,24.028,0,0,0,24-24V56A24.028,24.028,0,0,0,472,32Z" class="ci-primary"/>
</svg>

After

Width:  |  Height:  |  Size: 567 B

View File

@ -2,6 +2,7 @@
display: flex; display: flex;
height: 10px; height: 10px;
background: #eee; background: #eee;
margin-top: -5px;
// border-top: 1px solid #bbb; // border-top: 1px solid #bbb;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@ -1,24 +1,55 @@
.ant-tabs-top-bar{ .ant-tabs-top-bar {
display: flex; display: flex;
} }
.ant-tabs-extra-left{ .ant-tabs-extra-left {
order: 1; order: 1;
flex: 0 0 auto; flex: 0 0 auto;
} }
.ant-tabs-nav-container{ .ant-tabs-nav-container {
order: 2; order: 2;
flex: 0 0 auto; flex: 0 0 auto;
} }
.flex-tabbar .ant-tabs-top-bar .ant-tabs-nav-container{ .flex-tabbar {
.ant-tabs-top-bar .ant-tabs-nav-container {
max-width: calc(100% - 140px); 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; order: 3;
margin-left: 10px;
} }
.ant-tabs-extra-right{ .ant-tabs-extra-right {
order: 4; order: 4;
margin-left: auto; margin-left: auto;
flex: 0 0 auto; flex: 0 0 auto;
} }
#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;
}
}

View File

@ -55,9 +55,9 @@ const ConsoleWrapper = ({
<div style={{height: calcHeight}}> <div style={{height: calcHeight}}>
<div className="Console" style={{height:'100%'}}> <div className="Console" style={{height:'100%'}}>
<PanelsContainer resizerClassName="resizer"> <PanelsContainer resizerClassName="resizer">
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:30 }} initialWidth={INITIAL_PANEL_WIDTH}> <Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:26 }} initialWidth={INITIAL_PANEL_WIDTH}>
<ConsoleInput clusterID={selectedCluster.id} saveEditorContent={saveEditorContent} initialText={initialText} paneKey={paneKey} /> <ConsoleInput height={height-26+'px'} clusterID={selectedCluster.id} saveEditorContent={saveEditorContent} initialText={initialText} paneKey={paneKey} />
<div style={{background:'#fff', position:'absolute', left:0, bottom:0, width: '100%', height:30, zIndex:1001, borderTop: '1px solid #eee'}}> <div style={{background:'#fff', position:'absolute', left:0, bottom:0, width: '100%', height:26, zIndex:997, borderTop: '1px solid #eee'}}>
<RequestStatusBar <RequestStatusBar
requestInProgress={requestInProgress} requestInProgress={requestInProgress}
selectedCluster={selectedCluster} selectedCluster={selectedCluster}
@ -78,10 +78,10 @@ const ConsoleWrapper = ({
/> />
</div> </div>
</Panel> </Panel>
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:30 }} initialWidth={INITIAL_PANEL_WIDTH}> <Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:26 }} initialWidth={INITIAL_PANEL_WIDTH}>
<Tabs tabPosition='right' style={{height:'100%', width:'100%'}} size="small"> <Tabs tabPosition='right' style={{height:'100%', width:'100%'}} size="small">
<Tabs.TabPane tab="Result" key="result"> <Tabs.TabPane tab="Result" key="result">
<div style={{height:height-30}}> <div style={{height:height-26}}>
<ConsoleOutput clusterID={selectedCluster.id} /> <ConsoleOutput clusterID={selectedCluster.id} />
</div> </div>
@ -101,7 +101,7 @@ const ConsoleWrapper = ({
</Tabs> </Tabs>
</Tabs.TabPane> </Tabs.TabPane>
</Tabs> </Tabs>
<div style={{background:'#fff', position:'absolute', right:0, bottom:0, width: '100%', height:30, zIndex:1001, borderTop: '1px solid #eee'}}> <div style={{background:'#fff', position:'absolute', right:0, bottom:0, width: '100%', height:26, zIndex:997, borderTop: '1px solid #eee'}}>
<RequestStatusBar <RequestStatusBar
requestInProgress={requestInProgress} requestInProgress={requestInProgress}
selectedCluster={selectedCluster} selectedCluster={selectedCluster}

View File

@ -1,7 +1,7 @@
// @ts-ignore // @ts-ignore
import React, { useRef, useEffect, CSSProperties, useMemo } from 'react'; import React, { useRef, useEffect, CSSProperties, useMemo } from 'react';
import ace from 'brace'; import ace from 'brace';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip, PropertySortType } from '@elastic/eui';
import { SenseEditor } from '../entities/sense_editor'; import { SenseEditor } from '../entities/sense_editor';
import { LegacyCoreEditor } from '../modules/legacy_core_editor/legacy_core_editor'; import { LegacyCoreEditor } from '../modules/legacy_core_editor/legacy_core_editor';
import ConsoleMenu from './ConsoleMenu'; import ConsoleMenu from './ConsoleMenu';
@ -68,6 +68,7 @@ interface ConsoleInputProps {
initialText: string | undefined, initialText: string | undefined,
saveEditorContent: (content: string)=>void, saveEditorContent: (content: string)=>void,
paneKey: string, paneKey: string,
height?: string,
} }
const DEFAULT_INPUT_VALUE = `GET _search 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<HTMLDivElement | null>(null); const editorRef = useRef<HTMLDivElement | null>(null);
const editorActionsRef = useRef<HTMLDivElement | null>(null); const editorActionsRef = useRef<HTMLDivElement | null>(null);
const editorInstanceRef = useRef<SenseEditor | null>(null); const editorInstanceRef = useRef<SenseEditor | null>(null);
@ -167,7 +168,7 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:Con
return ( return (
<div style={abs} data-test-subj="console-application" className="conApp"> <div style={{...abs, height: height}} data-test-subj="console-application" className="conApp">
<div className="conApp__editor"> <div className="conApp__editor">
<ul className="conApp__autoComplete" id="autocomplete" /> <ul className="conApp__autoComplete" id="autocomplete" />
<EuiFlexGroup <EuiFlexGroup

View File

@ -161,17 +161,6 @@ export default class ConsoleMenu extends Component<Props, State> {
); );
const items = [ const items = [
<EuiContextMenuItem
key="Copy as cURL"
id="ConCopyAsCurl"
disabled={!window.navigator?.clipboard}
onClick={() => {
this.closePopover();
this.copyAsCurl();
}}
>
curl命令
</EuiContextMenuItem>,
<EuiContextMenuItem <EuiContextMenuItem
key="Auto indent" key="Auto indent"
onClick={this.autoIndent} onClick={this.autoIndent}
@ -185,6 +174,19 @@ export default class ConsoleMenu extends Component<Props, State> {
</EuiContextMenuItem>, </EuiContextMenuItem>,
]; ];
if(window.navigator?.clipboard){
items.unshift(<EuiContextMenuItem
key="Copy as cURL"
id="ConCopyAsCurl"
disabled={!window.navigator?.clipboard}
onClick={() => {
this.closePopover();
this.copyAsCurl();
}}
>
curl命令
</EuiContextMenuItem>)
}
return ( return (
<span onMouseEnter={this.mouseEnter}> <span onMouseEnter={this.mouseEnter}>

View File

@ -10,6 +10,7 @@
.info-item{ .info-item{
margin: 0 12px; margin: 0 12px;
position: relative; position: relative;
flex: 0 0 auto;
&.health{ &.health{
padding-right: 14px; padding-right: 14px;
} }

View File

@ -17,12 +17,19 @@
* under the License. * under the License.
*/ */
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from "react";
import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip,EuiCodeBlock } from '@elastic/eui'; import {
import {HealthStatusCircle} from '@/components/infini/health_status_circle'; EuiFlexGroup,
import './request_status_bar.scss'; EuiFlexItem,
import {Drawer, Tabs, Button} from 'antd'; EuiBadge,
import { FormattedMessage, formatMessage } from 'umi/locale'; 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 { export interface Props {
requestInProgress: boolean; requestInProgress: boolean;
@ -48,22 +55,22 @@ export interface Props {
const mapStatusCodeToBadgeColor = (statusCode: number) => { const mapStatusCodeToBadgeColor = (statusCode: number) => {
if (statusCode <= 199) { if (statusCode <= 199) {
return 'default'; return "default";
} }
if (statusCode <= 299) { if (statusCode <= 299) {
return 'secondary'; return "secondary";
} }
if (statusCode <= 399) { if (statusCode <= 399) {
return 'primary'; return "primary";
} }
if (statusCode <= 499) { if (statusCode <= 499) {
return 'warning'; return "warning";
} }
return 'danger'; return "danger";
}; };
export const RequestStatusBar = ({ export const RequestStatusBar = ({
@ -71,46 +78,61 @@ export const RequestStatusBar = ({
requestResult, requestResult,
selectedCluster, selectedCluster,
left, left,
}:Props) => { }: Props) => {
let content: React.ReactNode = null; let content: React.ReactNode = null;
const clusterContent = (<div className="base-info"> const clusterContent = (
<div className="base-info">
<div className="info-item health"> <div className="info-item health">
<span> <FormattedMessage id="console.cluster.status"/></span> <span>
<i style={{position:'absolute', top: 1, right:0}}> {" "}
<HealthStatusCircle status={selectedCluster.status}/> <FormattedMessage id="console.cluster.status" />
</span>
<i style={{ position: "absolute", top: 1, right: 0 }}>
<HealthStatusCircle status={selectedCluster.status} />
</i> </i>
</div> </div>
<div className="info-item"> <div className="info-item">
<span><FormattedMessage id="console.cluster.endpoint"/></span> <span>
<FormattedMessage id="console.cluster.endpoint" />
</span>
<EuiBadge color="default">{selectedCluster.host}</EuiBadge> <EuiBadge color="default">{selectedCluster.host}</EuiBadge>
</div> </div>
<div className="info-item"> <div className="info-item">
<span><FormattedMessage id="console.cluster.version"/></span> <span>
<FormattedMessage id="console.cluster.version" />
</span>
<EuiBadge color="default">{selectedCluster.version}</EuiBadge> <EuiBadge color="default">{selectedCluster.version}</EuiBadge>
</div> </div>
</div>); </div>
);
if (requestInProgress) { if (requestInProgress) {
content = ( content = (
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiBadge color="hollow"> <EuiBadge color="hollow">Request in progress</EuiBadge>
Request in progress
</EuiBadge>
</EuiFlexItem> </EuiFlexItem>
); );
} else if (requestResult) { } else if (requestResult) {
const { endpoint, method, statusCode, statusText, timeElapsedMs } = requestResult; const {
endpoint,
method,
statusCode,
statusText,
timeElapsedMs,
} = requestResult;
content = ( content = (
<> <>
<div className="status_info"> <div className="status_info">
<div className="info-item"> <div className="info-item">
<span><FormattedMessage id="console.response.status"/></span> <span>
<FormattedMessage id="console.response.status" />
</span>
<EuiToolTip <EuiToolTip
position="top" position="top"
content={ content={
<EuiText size="s">{`${method} ${ <EuiText size="s">{`${method} ${
endpoint.startsWith('/') ? endpoint : '/' + endpoint endpoint.startsWith("/") ? endpoint : "/" + endpoint
}`}</EuiText> }`}</EuiText>
} }
> >
@ -123,18 +145,16 @@ export const RequestStatusBar = ({
</EuiToolTip> </EuiToolTip>
</div> </div>
<div className="info-item"> <div className="info-item">
<span><FormattedMessage id="console.response.time_elapsed"/></span> <span>
<FormattedMessage id="console.response.time_elapsed" />
</span>
<EuiToolTip <EuiToolTip
position="top" position="top"
content={ content={<EuiText size="s">Time Elapsed</EuiText>}
<EuiText size="s">
Time Elapsed
</EuiText>
}
> >
<EuiText size="s"> <EuiText size="s">
<EuiBadge color="default"> <EuiBadge color="default">
{timeElapsedMs}&nbsp;{'ms'} {timeElapsedMs}&nbsp;{"ms"}
</EuiBadge> </EuiBadge>
</EuiText> </EuiText>
</EuiToolTip> </EuiToolTip>
@ -146,10 +166,11 @@ export const RequestStatusBar = ({
return ( return (
<div className="request-status-bar"> <div className="request-status-bar">
{left? <div className="bar-item">{clusterContent}</div>: {left ? (
[<div className="bar-item">{content}</div>,] <div className="bar-item">{clusterContent}</div>
} ) : (
<div className="bar-item">{content}</div>
)}
</div> </div>
); );
}; };

View File

@ -62,7 +62,7 @@ export const useSendCurrentRequestToES = () => {
} }
const {url, method, data} = requests[0]; const {url, method, data} = requests[0];
if(method === 'LOAD'){ 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 cmd = getCommand(rawUrl);
// const curPostion = editor.currentReqRange //(editor.getCoreEditor().getCurrentPosition()); // const curPostion = editor.currentReqRange //(editor.getCoreEditor().getCurrentPosition());
const lineNumber = editor.getCoreEditor().getCurrentPosition().lineNumber; const lineNumber = editor.getCoreEditor().getCurrentPosition().lineNumber;

View File

@ -17,14 +17,14 @@
* under the License. * under the License.
*/ */
import { SavedObjectsClientCommon } from '../..'; import { SavedObjectsClientCommon } from "../..";
import { createIndexPatternCache } from '.'; import { createIndexPatternCache } from ".";
import { IndexPattern } from './index_pattern'; import { IndexPattern } from "./index_pattern";
import { import {
createEnsureDefaultIndexPattern, createEnsureDefaultIndexPattern,
EnsureDefaultIndexPattern, EnsureDefaultIndexPattern,
} from './ensure_default_index_pattern'; } from "./ensure_default_index_pattern";
import { import {
OnNotification, OnNotification,
OnError, OnError,
@ -37,17 +37,17 @@ import {
FieldSpec, FieldSpec,
FieldFormatMap, FieldFormatMap,
IndexPatternFieldMap, IndexPatternFieldMap,
} from '../types'; } from "../types";
import { FieldFormatsStartCommon } from '../../field_formats'; import { FieldFormatsStartCommon } from "../../field_formats";
import { UI_SETTINGS, SavedObject } from '../../../common'; import { UI_SETTINGS, SavedObject } from "../../../common";
import { SavedObjectNotFound } from '../../../../kibana_utils/common'; import { SavedObjectNotFound } from "../../../../kibana_utils/common";
import { IndexPatternMissingIndices } from '../lib'; import { IndexPatternMissingIndices } from "../lib";
import { findByTitle } from '../utils'; import { findByTitle } from "../utils";
import { DuplicateIndexPatternError } from '../errors'; import { DuplicateIndexPatternError } from "../errors";
const indexPatternCache = createIndexPatternCache(); const indexPatternCache = createIndexPatternCache();
const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3;
const savedObjectType = 'view'; const savedObjectType = "view";
export interface IndexPatternSavedObjectAttrs { export interface IndexPatternSavedObjectAttrs {
title: string; title: string;
@ -67,7 +67,9 @@ interface IndexPatternsServiceDeps {
export class IndexPatternsService { export class IndexPatternsService {
private config: UiSettingsCommon; private config: UiSettingsCommon;
private savedObjectsClient: SavedObjectsClientCommon; private savedObjectsClient: SavedObjectsClientCommon;
private savedObjectsCache?: Array<SavedObject<IndexPatternSavedObjectAttrs>> | null; private savedObjectsCache?: Array<
SavedObject<IndexPatternSavedObjectAttrs>
> | null;
private apiClient: IIndexPatternsApiClient; private apiClient: IIndexPatternsApiClient;
private fieldFormats: FieldFormatsStartCommon; private fieldFormats: FieldFormatsStartCommon;
private onNotification: OnNotification; private onNotification: OnNotification;
@ -102,9 +104,11 @@ export class IndexPatternsService {
* Refresh cache of index pattern ids and titles * Refresh cache of index pattern ids and titles
*/ */
private async refreshSavedObjectsCache() { private async refreshSavedObjectsCache() {
this.savedObjectsCache = await this.savedObjectsClient.find<IndexPatternSavedObjectAttrs>({ this.savedObjectsCache = await this.savedObjectsClient.find<
type: 'index-pattern', IndexPatternSavedObjectAttrs
fields: ['title'], >({
type: "index-pattern",
fields: ["title"],
perPage: 10000, perPage: 10000,
}); });
} }
@ -180,7 +184,7 @@ export class IndexPatternsService {
* Get default index pattern * Get default index pattern
*/ */
getDefault = async () => { getDefault = async () => {
const defaultIndexPatternId = await this.config.get('defaultIndex'); const defaultIndexPatternId = await this.config.get("defaultIndex");
if (defaultIndexPatternId) { if (defaultIndexPatternId) {
return await this.get(defaultIndexPatternId); return await this.get(defaultIndexPatternId);
} }
@ -194,8 +198,8 @@ export class IndexPatternsService {
* @param force * @param force
*/ */
setDefault = async (id: string, force = false) => { setDefault = async (id: string, force = false) => {
if (force || !this.config.get('defaultIndex')) { if (force || !this.config.get("defaultIndex")) {
await this.config.set('defaultIndex', id); await this.config.set("defaultIndex", id);
} }
}; };
@ -206,10 +210,10 @@ export class IndexPatternsService {
return Object.values(specs).every((spec) => { return Object.values(specs).every((spec) => {
// See https://github.com/elastic/kibana/pull/8421 // 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 // See https://github.com/elastic/kibana/pull/11969
const hasDocValuesFlag = 'readFromDocValues' in spec; const hasDocValuesFlag = "readFromDocValues" in spec;
return !hasFieldCaps || !hasDocValuesFlag; return !hasFieldCaps || !hasDocValuesFlag;
}); });
@ -251,15 +255,22 @@ export class IndexPatternsService {
refreshFields = async (indexPattern: IndexPattern) => { refreshFields = async (indexPattern: IndexPattern) => {
try { try {
const fields = await this.getFieldsForIndexPattern(indexPattern); 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]); indexPattern.fields.replaceAll([...fields, ...scripted]);
} catch (err) { } catch (err) {
if (err instanceof IndexPatternMissingIndices) { 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, { 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, title: string,
options: GetFieldsOptions options: GetFieldsOptions
) => { ) => {
const scriptdFields = Object.values(fields).filter((field) => field.scripted); const scriptdFields = Object.values(fields).filter(
(field) => field.scripted
);
try { try {
const newFields = await this.getFieldsForWildcard(options); const newFields = await this.getFieldsForWildcard(options);
return this.fieldArrayToMap([...newFields, ...scriptdFields]); return this.fieldArrayToMap([...newFields, ...scriptdFields]);
} catch (err) { } catch (err) {
if (err instanceof IndexPatternMissingIndices) { 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 {}; return {};
} }
this.onError(err, { 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; return fields;
@ -299,7 +316,10 @@ export class IndexPatternsService {
* @param fieldSpecs * @param fieldSpecs
* @param fieldFormatMap * @param fieldFormatMap
*/ */
private addFormatsToFields = (fieldSpecs: FieldSpec[], fieldFormatMap: FieldFormatMap) => { private addFormatsToFields = (
fieldSpecs: FieldSpec[],
fieldFormatMap: FieldFormatMap
) => {
Object.entries(fieldFormatMap).forEach(([fieldName, value]) => { Object.entries(fieldFormatMap).forEach(([fieldName, value]) => {
const field = fieldSpecs.find((fld: FieldSpec) => fld.name === fieldName); const field = fieldSpecs.find((fld: FieldSpec) => fld.name === fieldName);
if (field) { if (field) {
@ -323,7 +343,9 @@ export class IndexPatternsService {
* @param savedObject * @param savedObject
*/ */
savedObjectToSpec = (savedObject: SavedObject<IndexPatternAttributes>): IndexPatternSpec => { savedObjectToSpec = (
savedObject: SavedObject<IndexPatternAttributes>
): IndexPatternSpec => {
const { const {
id, id,
version, version,
@ -336,13 +358,17 @@ export class IndexPatternsService {
sourceFilters, sourceFilters,
fieldFormatMap, fieldFormatMap,
typeMeta, typeMeta,
type,
}, },
type,
} = savedObject; } = savedObject;
const parsedSourceFilters = sourceFilters ? JSON.parse(sourceFilters) : undefined; const parsedSourceFilters = sourceFilters
? JSON.parse(sourceFilters)
: undefined;
const parsedTypeMeta = typeMeta ? JSON.parse(typeMeta) : 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) : []; const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : [];
this.addFormatsToFields(parsedFields, parsedFieldFormatMap); this.addFormatsToFields(parsedFields, parsedFieldFormatMap);
@ -365,16 +391,20 @@ export class IndexPatternsService {
* @param id * @param id
*/ */
get = async (id: string): Promise<IndexPattern> => { get = async (
const cache = indexPatternCache.get(id); id: string,
typ: string,
clusterID: string
): Promise<IndexPattern> => {
const cacheID = typ == "index" ? clusterID + id : id;
const cache = indexPatternCache.get(cacheID);
if (cache) { if (cache) {
return cache; return cache;
} }
const savedObject = await this.savedObjectsClient.get<IndexPatternAttributes>( const savedObject = await this.savedObjectsClient.get<
savedObjectType, IndexPatternAttributes
id >(typ || savedObjectType, id);
);
// if (!savedObject.version) { // if (!savedObject.version) {
// throw new SavedObjectNotFound(savedObjectType, id, 'management/kibana/indexPatterns'); // throw new SavedObjectNotFound(savedObjectType, id, 'management/kibana/indexPatterns');
@ -382,28 +412,34 @@ export class IndexPatternsService {
const spec = this.savedObjectToSpec(savedObject); const spec = this.savedObjectToSpec(savedObject);
const { title, type, typeMeta } = spec; const { title, type, typeMeta } = spec;
const parsedFieldFormats: FieldFormatMap = savedObject.attributes.fieldFormatMap const parsedFieldFormats: FieldFormatMap = savedObject.attributes
.fieldFormatMap
? JSON.parse(savedObject.attributes.fieldFormatMap) ? JSON.parse(savedObject.attributes.fieldFormatMap)
: {}; : {};
const isFieldRefreshRequired =
const isFieldRefreshRequired = this.isFieldRefreshRequired(spec.fields); spec.type == "index" ? false : this.isFieldRefreshRequired(spec.fields);
let isSaveRequired = isFieldRefreshRequired; let isSaveRequired = isFieldRefreshRequired;
try { try {
spec.fields = isFieldRefreshRequired spec.fields = isFieldRefreshRequired
? await this.refreshFieldSpecMap(spec.fields || {}, id, spec.title as string, { ? await this.refreshFieldSpecMap(
spec.fields || {},
id,
spec.title as string,
{
pattern: title, pattern: title,
metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), metaFields: await this.config.get(UI_SETTINGS.META_FIELDS),
type, type,
params: typeMeta && typeMeta.params, params: typeMeta && typeMeta.params,
}) }
)
: spec.fields; : spec.fields;
} catch (err) { } catch (err) {
isSaveRequired = false; isSaveRequired = false;
if (err instanceof IndexPatternMissingIndices) { if (err instanceof IndexPatternMissingIndices) {
this.onNotification({ this.onNotification({
title: (err as any).message, title: (err as any).message,
color: 'danger', color: "danger",
iconType: 'alert', iconType: "alert",
}); });
} else { } else {
this.onError(err, { this.onError(err, {
@ -468,8 +504,13 @@ export class IndexPatternsService {
* @param spec * @param spec
* @param skipFetchFields * @param skipFetchFields
*/ */
async create(spec: IndexPatternSpec, skipFetchFields = false): Promise<IndexPattern> { async create(
const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); spec: IndexPatternSpec,
skipFetchFields = false
): Promise<IndexPattern> {
const shortDotsEnable = await this.config.get(
UI_SETTINGS.SHORT_DOTS_ENABLE
);
const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS);
const indexPattern = new IndexPattern({ const indexPattern = new IndexPattern({
@ -488,11 +529,13 @@ export class IndexPatternsService {
} }
find = async (search: string, size: number = 10): Promise<IndexPattern[]> => { find = async (search: string, size: number = 10): Promise<IndexPattern[]> => {
const savedObjects = await this.savedObjectsClient.find<IndexPatternSavedObjectAttrs>({ const savedObjects = await this.savedObjectsClient.find<
type: 'index-pattern', IndexPatternSavedObjectAttrs
fields: ['title'], >({
type: "index-pattern",
fields: ["title"],
search, search,
searchFields: ['title'], searchFields: ["title"],
perPage: size, perPage: size,
}); });
const getIndexPatternPromises = savedObjects.map(async (savedObject) => { const getIndexPatternPromises = savedObjects.map(async (savedObject) => {
@ -508,7 +551,11 @@ export class IndexPatternsService {
* @param skipFetchFields * @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); const indexPattern = await this.create(spec, skipFetchFields);
await this.createSavedObject(indexPattern, override); await this.createSavedObject(indexPattern, override);
await this.setDefault(indexPattern.id as string); await this.setDefault(indexPattern.id as string);
@ -527,14 +574,20 @@ export class IndexPatternsService {
if (override) { if (override) {
await this.delete(dupe.id); await this.delete(dupe.id);
} else { } else {
throw new DuplicateIndexPatternError(`Duplicate index pattern: ${indexPattern.title}`); throw new DuplicateIndexPatternError(
`Duplicate index pattern: ${indexPattern.title}`
);
} }
} }
const body = indexPattern.getAsSavedObjectBody(); const body = indexPattern.getAsSavedObjectBody();
const response = await this.savedObjectsClient.create(savedObjectType, body, { const response = await this.savedObjectsClient.create(
savedObjectType,
body,
{
id: indexPattern.id, id: indexPattern.id,
}); }
);
indexPattern.id = response.id; indexPattern.id = response.id;
indexPatternCache.set(indexPattern.id, indexPattern); indexPatternCache.set(indexPattern.id, indexPattern);
return indexPattern; return indexPattern;
@ -551,6 +604,7 @@ export class IndexPatternsService {
saveAttempts: number = 0, saveAttempts: number = 0,
ignoreErrors: boolean = false ignoreErrors: boolean = false
): Promise<void | Error> { ): Promise<void | Error> {
debugger;
if (!indexPattern.id) return; if (!indexPattern.id) return;
// get the list of attributes // get the list of attributes
@ -566,13 +620,18 @@ export class IndexPatternsService {
}); });
return this.savedObjectsClient return this.savedObjectsClient
.update(savedObjectType, indexPattern.id, body, { version: indexPattern.version }) .update(savedObjectType, indexPattern.id, body, {
version: indexPattern.version,
})
.then((resp) => { .then((resp) => {
indexPattern.id = resp.id; indexPattern.id = resp.id;
indexPattern.version = resp.version; indexPattern.version = resp.version;
}) })
.catch(async (err) => { .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); const samePattern = await this.get(indexPattern.id as string);
// What keys changed from now and what the server returned // What keys changed from now and what the server returned
const updatedBody = samePattern.getAsSavedObjectBody(); const updatedBody = samePattern.getAsSavedObjectBody();
@ -584,7 +643,10 @@ export class IndexPatternsService {
const serverChangedKeys: string[] = []; const serverChangedKeys: string[] = [];
Object.entries(updatedBody).forEach(([key, value]) => { 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); serverChangedKeys.push(key);
} }
}); });
@ -604,9 +666,9 @@ export class IndexPatternsService {
return; return;
} }
const title = const title =
'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.'; "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; throw err;
} }
@ -620,7 +682,11 @@ export class IndexPatternsService {
indexPatternCache.clear(indexPattern.id!); indexPatternCache.clear(indexPattern.id!);
// Try the save again // Try the save again
return this.updateSavedObject(indexPattern, saveAttempts, ignoreErrors); return this.updateSavedObject(
indexPattern,
saveAttempts,
ignoreErrors
);
} }
throw err; throw err;
}); });

View File

@ -185,7 +185,7 @@ function FilterBarUI(props: Props) {
alignItems="flexStart" alignItems="flexStart"
responsive={false} responsive={false}
> >
<EuiFlexItem className="globalFilterGroup__branch" grow={false}> <EuiFlexItem className="globalFilterGroup__branch" grow={false} style={{padding:0, alignSelf:'center'}}>
<FilterOptions <FilterOptions
onEnableAll={onEnableAll} onEnableAll={onEnableAll}
onDisableAll={onDisableAll} onDisableAll={onDisableAll}

View File

@ -1,10 +1,11 @@
import { EuiIcon } from "@elastic/eui"; import { EuiIcon } from "@elastic/eui";
import { TableHeader } from "./table_header/table_header"; import { TableHeader } from "./table_header/table_header";
import { SortOrder } from "./table_header/helpers"; import { SortOrder } from "./table_header/helpers";
import './_doc_table.scss'; import "./_doc_table.scss";
import {TableRow} from './table_row/table_row'; import { TableRow } from "./table_row/table_row";
import { useState, useEffect} from 'react'; import React, { useState, useEffect } from "react";
import InfiniteScroll from 'react-infinite-scroll-component'; import InfiniteScroll from "react-infinite-scroll-component";
import TableContext from "./table_context";
interface TableProps { interface TableProps {
columns: string[]; columns: string[];
@ -21,49 +22,69 @@ interface TableProps {
const pageCount = 50; const pageCount = 50;
const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, onFilter, onMoveColumn, onAddColumn, const Table: React.FC<TableProps> = ({
onRemoveColumn, onChangeSortOrder, document }) => { columns,
const [scrollState, setScrollState] = useState({limit: pageCount, hasMore: true}); hits,
useEffect(()=>{ sortOrder,
indexPattern,
onFilter,
onMoveColumn,
onAddColumn,
onRemoveColumn,
onChangeSortOrder,
document,
}) => {
const [scrollState, setScrollState] = useState({
limit: pageCount,
hasMore: true,
});
useEffect(() => {
setScrollState({ setScrollState({
limit: pageCount, limit: pageCount,
hasMore: hits.length > pageCount, hasMore: hits.length > pageCount,
}) });
},[indexPattern, hits]) }, [indexPattern, hits]);
const tableRef = React.useRef(null);
return ( return (
<InfiniteScroll <InfiniteScroll
dataLength={scrollState.limit} dataLength={scrollState.limit}
next={()=>{ next={() => {
const newLimit = scrollState.limit + pageCount; const newLimit = scrollState.limit + pageCount;
setScrollState({limit: newLimit, hasMore: newLimit < hits.length}); setScrollState({ limit: newLimit, hasMore: newLimit < hits.length });
}} }}
hasMore={scrollState.hasMore} hasMore={scrollState.hasMore}
loader={<h4 style={{textAlign: 'center', margin: '10px auto'}}>Loading...</h4>} loader={
<h4 style={{ textAlign: "center", margin: "10px auto" }}>Loading...</h4>
}
endMessage={ endMessage={
<p style={{ textAlign: 'center' }}> <p style={{ textAlign: "center" }}>{/* <b>no more data</b> */}</p>
{/* <b>no more data</b> */} }
</p> >
}> <div ref={tableRef}>
<div>
{hits.length ? ( {hits.length ? (
<div> <div>
<TableContext.Provider value={{ tableRef: tableRef.current }}>
<table className="kbn-table table"> <table className="kbn-table table">
<thead> <thead>
<TableHeader columns={columns} <TableHeader
defaultSortOrder={''} columns={columns}
defaultSortOrder={"desc"}
hideTimeColumn={false} hideTimeColumn={false}
indexPattern={indexPattern} indexPattern={indexPattern}
isShortDots={false} isShortDots={false}
onChangeSortOrder={onChangeSortOrder} onChangeSortOrder={onChangeSortOrder}
onMoveColumn={onMoveColumn} onMoveColumn={onMoveColumn}
onRemoveColumn={onRemoveColumn} onRemoveColumn={onRemoveColumn}
sortOrder={sortOrder||[]}/> sortOrder={sortOrder || []}
/>
</thead> </thead>
<tbody> <tbody>
{hits.slice(0, scrollState.limit).map((row, idx)=>{ {hits.slice(0, scrollState.limit).map((row, idx) => {
return (
return <TableRow key={'discover-table-row'+row._id} onFilter={onFilter} <TableRow
key={"discover-table-row" + row._id}
onFilter={onFilter}
columns={columns} columns={columns}
hideTimeColumn={false} hideTimeColumn={false}
indexPattern={indexPattern} indexPattern={indexPattern}
@ -73,9 +94,11 @@ const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, o
row={row} row={row}
document={document} document={document}
/> />
);
})} })}
</tbody> </tbody>
</table> </table>
</TableContext.Provider>
</div> </div>
) : null} ) : null}
@ -93,4 +116,4 @@ const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, o
); );
}; };
export default Table; export default React.memo(Table);

View File

@ -0,0 +1,7 @@
import * as React from "react";
const TableContext = React.createContext({
tableRef: null,
});
export default TableContext;

View File

@ -8,7 +8,6 @@
// Overwrite the border on the bootstrap table // Overwrite the border on the bootstrap table
.kbnDocTableDetails__row { .kbnDocTableDetails__row {
> td { > td {
// Offsets negative margins from an inner flex group // Offsets negative margins from an inner flex group
padding: 16px !important; padding: 16px !important;
@ -22,4 +21,3 @@
border-top: none !important; border-top: none !important;
} }
} }

View File

@ -1,15 +1,29 @@
import {EuiIcon} from '@elastic/eui'; import { EuiIcon } from "@elastic/eui";
import { DocViewer } from '../../doc_viewer/doc_viewer'; import { DocViewer } from "../../doc_viewer/doc_viewer";
import {Drawer, Button, Menu,Dropdown, Icon, Popconfirm, message,Descriptions, Popover, Input} from 'antd'; import {
Drawer,
Button,
Menu,
Dropdown,
Icon,
Popconfirm,
message,
Descriptions,
Popover,
Input,
} from "antd";
import Editor from "@monaco-editor/react"; import Editor from "@monaco-editor/react";
import {useState, useRef} from 'react'; import { useState, useRef } from "react";
function generateNewID(id: string) { 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 { interface Props {
columns: string[]; columns: string[];
indexPattern: any; indexPattern: any;
@ -28,7 +42,7 @@ export function Detail({
onAddColumn, onAddColumn,
onRemoveColumn, onRemoveColumn,
document, document,
}:Props){ }: Props) {
const [editorVisible, setEditorVisble] = useState(false); const [editorVisible, setEditorVisble] = useState(false);
const editorRef = useRef(null); const editorRef = useRef(null);
@ -36,20 +50,20 @@ export function Detail({
editorRef.current = editor; editorRef.current = editor;
} }
const editDocumentClick = ()=>{ const editDocumentClick = () => {
setEditorVisble(true) setEditorVisble(true);
} };
const editCancelClick = ()=>{ const editCancelClick = () => {
setEditorVisble(false) setEditorVisble(false);
} };
const saveDocumentClick = async (docID?: string)=>{ const saveDocumentClick = async (docID?: string) => {
const value = editorRef.current?.getValue(); const value = editorRef.current?.getValue();
let source = {} let source = {};
try { try {
source = JSON.parse(value) source = JSON.parse(value);
} catch (error) { } catch (error) {
message.error('wrong json format') message.error("wrong json format");
return return;
} }
let params = { let params = {
_index: row._index, _index: row._index,
@ -58,17 +72,17 @@ export function Detail({
_source: source, _source: source,
}; };
docID && (params['is_new'] = '1') docID && (params["is_new"] = "1");
const res = await document.saveDocument(params) const res = await document.saveDocument(params);
if(!res.error) setEditorVisble(false) if (!res.error) setEditorVisble(false);
} };
const deleteDocumentClick = ()=>{ const deleteDocumentClick = () => {
document.deleteDocument({ document.deleteDocument({
_index: row._index, _index: row._index,
_id: row._id, _id: row._id,
_type: row._type, _type: row._type,
}) });
} };
const menu = ( const menu = (
<Menu> <Menu>
@ -76,35 +90,58 @@ export function Detail({
<a> Edit </a> <a> Edit </a>
</Menu.Item> </Menu.Item>
<Menu.Item key="Delete"> <Menu.Item key="Delete">
<Popconfirm title="sure to delete" onConfirm={()=>{ <Popconfirm
title="sure to delete"
onConfirm={() => {
deleteDocumentClick(); deleteDocumentClick();
}}><a> Delete </a></Popconfirm> }}
>
<a> Delete </a>
</Popconfirm>
</Menu.Item> </Menu.Item>
</Menu> </Menu>
); );
return ( return (
<td colSpan={ columns.length + 2 }> <td
colSpan={columns.length + 2}
style={{
wordBreak: "break-all",
wordWrap: "break-word",
whiteSpace: "pre-wrap",
}}
>
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween"> <div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween">
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small"> <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
<div className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow"> <div className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow">
<div className="euiFlexItem euiFlexItem--flexGrowZero"> <div className="euiFlexItem euiFlexItem--flexGrowZero">
<EuiIcon type="folderOpen" size="m" style={{marginTop: 5}}/> <EuiIcon type="folderOpen" size="m" style={{ marginTop: 5 }} />
</div> </div>
<div className="euiFlexItem euiFlexItem--flexGrowZero"> <div className="euiFlexItem euiFlexItem--flexGrowZero">
<h4 <h4
data-test-subj="docTableRowDetailsTitle" data-test-subj="docTableRowDetailsTitle"
className="euiTitle euiTitle--xsmall" className="euiTitle euiTitle--xsmall"
>Expanded document</h4> >
Expanded document
</h4>
</div> </div>
</div> </div>
</div> </div>
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small"> <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow"> <div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow">
<Drawer title="Edit document" visible={editorVisible} width="640" destroyOnClose={true} <Drawer
onClose={()=>{setEditorVisble(false)}}> title="Edit document"
visible={editorVisible}
width="640"
destroyOnClose={true}
onClose={() => {
setEditorVisble(false);
}}
>
<Descriptions> <Descriptions>
<Descriptions.Item label="_index">{row._index}</Descriptions.Item> <Descriptions.Item label="_index">
{row._index}
</Descriptions.Item>
<Descriptions.Item label="_id">{row._id}</Descriptions.Item> <Descriptions.Item label="_id">{row._id}</Descriptions.Item>
</Descriptions> </Descriptions>
<Editor <Editor
@ -121,12 +158,31 @@ export function Detail({
value={JSON.stringify(row._source, null, 2)} value={JSON.stringify(row._source, null, 2)}
onMount={handleEditorDidMount} onMount={handleEditorDidMount}
/> />
<div style={{display:'flex', height: '10vh', alignItems:'center', justifyContent:'center'}}> <div
<div style={{marginLeft:'auto'}} > style={{
<Button onClick={editCancelClick} style={{marginRight:5}}>Cancel</Button> display: "flex",
height: "10vh",
alignItems: "center",
justifyContent: "center",
}}
>
<div style={{ marginLeft: "auto" }}>
<Button onClick={editCancelClick} style={{ marginRight: 5 }}>
Cancel
</Button>
{/* <Button type="primary" onClick={()=>{}} style={{marginRight:5}}>Save as New</Button> */} {/* <Button type="primary" onClick={()=>{}} style={{marginRight:5}}>Save as New</Button> */}
<SaveAsNewButton docID={row._id} saveDocumentClick={saveDocumentClick}/> <SaveAsNewButton
<Button type="primary" onClick={()=>{saveDocumentClick()}} >Save</Button> docID={row._id}
saveDocumentClick={saveDocumentClick}
/>
<Button
type="primary"
onClick={() => {
saveDocumentClick();
}}
>
Save
</Button>
</div> </div>
</div> </div>
</Drawer> </Drawer>
@ -135,8 +191,11 @@ export function Detail({
className="euiLink" className="euiLink"
onClick={()=>{setEditorVisble(true)}} onClick={()=>{setEditorVisble(true)}}
>Edit document</a> */} >Edit document</a> */}
<Dropdown overlay={menu} > <Dropdown overlay={menu}>
<a className="ant-dropdown-link" onClick={e => e.preventDefault()}> <a
className="ant-dropdown-link"
onClick={(e) => e.preventDefault()}
>
Operation <Icon type="down" /> Operation <Icon type="down" />
</a> </a>
</Dropdown> </Dropdown>
@ -164,23 +223,34 @@ export function Detail({
onRemoveColumn={onRemoveColumn} onRemoveColumn={onRemoveColumn}
/> />
</div> </div>
</td>
</td> );
)
} }
const SaveAsNewButton = ({docID, saveDocumentClick}:any)=>{ const SaveAsNewButton = ({ docID, saveDocumentClick }: any) => {
const newID = generateNewID(docID); const newID = generateNewID(docID);
const [newDocID, setNewDocID] = useState(newID) const [newDocID, setNewDocID] = useState(newID);
const content = (<div style={{width: 200}}> const content = (
<div><Input value={newDocID} onChange={(e)=>{ <div style={{ width: 200 }}>
setNewDocID(e.target.value) <div>
}} /></div> <Input
<div style={{marginTop:10}}><Button onClick={()=>{ value={newDocID}
saveDocumentClick(newDocID) onChange={(e) => {
}}></Button></div> setNewDocID(e.target.value);
</div>) }}
/>
</div>
<div style={{ marginTop: 10 }}>
<Button
onClick={() => {
saveDocumentClick(newDocID);
}}
>
</Button>
</div>
</div>
);
return ( return (
<Popover <Popover
content={content} content={content}
@ -189,7 +259,9 @@ const SaveAsNewButton = ({docID, saveDocumentClick}:any)=>{
// visible={this.state.visible} // visible={this.state.visible}
// onVisibleChange={this.handleVisibleChange} // onVisibleChange={this.handleVisibleChange}
> >
<Button style={{marginRight:5}} type="primary">Save as new</Button> <Button style={{ marginRight: 5 }} type="primary">
Save as new
</Button>
</Popover> </Popover>
) );
} };

View File

@ -1,7 +1,9 @@
import {Open} from './open'; import { Open } from "./open";
import {Cell} from './cell'; import { Cell } from "./cell";
import {Detail} from './detail'; import { Detail } from "./detail";
import {useState} from 'react'; import React, { useState } from "react";
const MemoDetail = React.memo(Detail);
interface Props { interface Props {
// sorting="sorting" // sorting="sorting"
@ -25,61 +27,88 @@ export function TableRow({
onAddColumn, onAddColumn,
onRemoveColumn, onRemoveColumn,
row, row,
document document,
}:Props){ }: Props) {
const mapping = indexPattern.fields.getByName; const mapping = indexPattern.fields.getByName;
const [open,setOpen] = useState(false); const [open, setOpen] = useState(false);
return ( return (
<> <>
<tr <tr className="kbnDocTable__row">
className="kbnDocTable__row" <Open
> open={open}
<Open open={open} onClick={()=>{ onClick={() => {
setOpen(!open); setOpen(!open);
}}/> }}
{(indexPattern.timeFieldName && !hideTimeColumn)? <Cell timefield={true} />
{indexPattern.timeFieldName && !hideTimeColumn ? (
<Cell
timefield={true}
row={row} row={row}
indexPattern={indexPattern} indexPattern={indexPattern}
inlineFilter={onFilter} inlineFilter={onFilter}
formatted={_displayField(indexPattern, row, indexPattern.timeFieldName)} formatted={_displayField(
indexPattern,
row,
indexPattern.timeFieldName
)}
filterable={mapping(indexPattern.timeFieldName).filterable} //&& $scope.filter filterable={mapping(indexPattern.timeFieldName).filterable} //&& $scope.filter
column= {indexPattern.timeFieldName}/>: null} column={indexPattern.timeFieldName}
/>
) : null}
{columns.map(function (column: any) { {columns.map(function(column: any) {
const isFilterable = mapping(column) && mapping(column).filterable ;//&& $scope.filter; const isFilterable = mapping(column) && mapping(column).filterable; //&& $scope.filter;
return <Cell key={'discover-cell-'+column} timefield={false} return (
<Cell
key={"discover-cell-" + column}
timefield={false}
row={row} row={row}
inlineFilter={onFilter} inlineFilter={onFilter}
indexPattern={indexPattern} indexPattern={indexPattern}
sourcefield={column === '_source'} sourcefield={column === "_source"}
formatted={_displayField(indexPattern, row, column, true)} formatted={_displayField(indexPattern, row, column, true)}
filterable={isFilterable} //&& $scope.filter filterable={isFilterable} //&& $scope.filter
column= {column}/> column={column}
/>
);
})} })}
</tr> </tr>
{open? <tr className="kbnDocTableDetails__row"> {open && (
<Detail columns={columns} <tr className="kbnDocTableDetails__row">
<MemoDetail
columns={columns}
indexPattern={indexPattern} indexPattern={indexPattern}
row={row} row={row}
document={document} document={document}
onFilter={onFilter} onFilter={onFilter}
onAddColumn={onAddColumn} onAddColumn={onAddColumn}
onRemoveColumn={onRemoveColumn}/> onRemoveColumn={onRemoveColumn}
</tr>:null />
} </tr>
)}
</> </>
) );
} }
const MIN_LINE_LENGTH = 20; 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); const text = indexPattern.formatField(row, fieldName);
if (truncate && text.length > MIN_LINE_LENGTH) { if (truncate && text.length > MIN_LINE_LENGTH) {
return <div className="truncate-by-height" dangerouslySetInnerHTML={{ __html: text }} > return (
</div> <div
className="truncate-by-height"
dangerouslySetInnerHTML={{ __html: text }}
></div>
);
} }
return <span dangerouslySetInnerHTML={{ __html: text }} />; return <span dangerouslySetInnerHTML={{ __html: text }} />;

View File

@ -16,12 +16,13 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import './doc_viewer.scss'; import "./doc_viewer.scss";
import React from 'react'; import React from "react";
import { EuiTabbedContent } from '@elastic/eui'; import { EuiTabbedContent } from "@elastic/eui";
import { getDocViewsRegistry } from '../../../kibana_services'; import { getDocViewsRegistry } from "../../../kibana_services";
import { DocViewerTab } from './doc_viewer_tab'; import { DocViewerTab } from "./doc_viewer_tab";
import { DocView, DocViewRenderProps } from '../../doc_views/doc_views_types'; 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. * 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. // This condition takes care of unit tests with 0 tabs.
return null; 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 ( return (
<div className="kbnDocViewer"> <div className="kbnDocViewer" style={{ width: viewerWidth }}>
<EuiTabbedContent tabs={tabs} /> <EuiTabbedContent tabs={tabs} />
</div> </div>
); );

View File

@ -1,6 +1,6 @@
import moment, { unitOfTime } from 'moment-timezone'; import moment, { unitOfTime } from "moment-timezone";
import React, { Component } from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import { import {
Axis, Axis,
@ -15,27 +15,26 @@ import {
BrushEndListener, BrushEndListener,
Theme, Theme,
LIGHT_THEME, LIGHT_THEME,
} from '@elastic/charts'; } from "@elastic/charts";
import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; import lightEuiTheme from "@elastic/eui/dist/eui_theme_light.json";
import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; import darkEuiTheme from "@elastic/eui/dist/eui_theme_dark.json";
import "@elastic/charts/dist/theme_light.css"; import "@elastic/charts/dist/theme_light.css";
import { Subscription, combineLatest } from 'rxjs'; import { Subscription, combineLatest } from "rxjs";
import {CurrentTime} from './current_time'; import { CurrentTime } from "./current_time";
import { import {
Endzones, Endzones,
getAdjustedInterval, getAdjustedInterval,
renderEndzoneTooltip, renderEndzoneTooltip,
} from './endzones'; } from "./endzones";
function getTimezone() { function getTimezone() {
const detectedTimezone = moment.tz.guess(); const detectedTimezone = moment.tz.guess();
if (detectedTimezone) return detectedTimezone; if (detectedTimezone) return detectedTimezone;
else return moment().format('Z'); else return moment().format("Z");
} }
export class DiscoverHistogram extends Component{ export class DiscoverHistogram extends Component {
static propTypes = { static propTypes = {
chartData: PropTypes.object, chartData: PropTypes.object,
timefilterUpdateHandler: PropTypes.func, timefilterUpdateHandler: PropTypes.func,
@ -71,7 +70,7 @@ export class DiscoverHistogram extends Component{
}; };
onElementClick = (xInterval) => ([elementData]) => { onElementClick = (xInterval) => ([elementData]) => {
const startRange = (elementData)[0].x; const startRange = elementData[0].x;
const range = { const range = {
from: startRange, from: startRange,
@ -130,21 +129,26 @@ export class DiscoverHistogram extends Component{
), ),
}; };
const tooltipProps = { const tooltipProps = {
headerFormatter: renderEndzoneTooltip(xInterval, domainStart, domainEnd, this.formatXValue), headerFormatter: renderEndzoneTooltip(
xInterval,
domainStart,
domainEnd,
this.formatXValue
),
type: TooltipType.VerticalCursor, type: TooltipType.VerticalCursor,
}; };
// const xAxisFormatter = getServices().data.fieldFormats.deserialize( // const xAxisFormatter = getServices().data.fieldFormats.deserialize(
// this.props.chartData.yAxisFormat // this.props.chartData.yAxisFormat
// ); // );
const xAxisFormatter = { const xAxisFormatter = {
convert: (value)=>{ convert: (value) => {
return value; return value;
} },
} };
//console.log(data) //console.log(data)
return ( return (
<Chart size="100%" size={{height:200}}> <Chart size="100%" size={{ height: 120 }}>
<Settings <Settings
xDomain={xDomain} xDomain={xDomain}
onBrushEnd={this.onBrushEnd} onBrushEnd={this.onBrushEnd}
@ -159,13 +163,15 @@ export class DiscoverHistogram extends Component{
ticks={5} ticks={5}
title={chartData.yAxisLabel} title={chartData.yAxisLabel}
integersOnly integersOnly
tickFormat={(value) => {return xAxisFormatter.convert(value)}} tickFormat={(value) => {
return xAxisFormatter.convert(value);
}}
showGridLines showGridLines
/> />
<Axis <Axis
id="discover-histogram-bottom-axis" id="discover-histogram-bottom-axis"
position={Position.Bottom} position={Position.Bottom}
title={chartData.xAxisLabel} // title={chartData.xAxisLabel}
tickFormat={this.formatXValue} tickFormat={this.formatXValue}
ticks={10} ticks={10}
//showGridLines //showGridLines
@ -185,12 +191,11 @@ export class DiscoverHistogram extends Component{
xScaleType={ScaleType.Time} xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear} yScaleType={ScaleType.Linear}
xAccessor="x" xAccessor="x"
yAccessors={['y']} yAccessors={["y"]}
data={data} data={data}
timeZone={timeZone} timeZone={timeZone}
name={chartData.yAxisLabel} name={chartData.yAxisLabel}
/> />
</Chart> </Chart>
); );
} }

View File

@ -16,12 +16,12 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React from 'react'; import React from "react";
import { EuiCodeBlock } from '@elastic/eui'; import { EuiCodeBlock } from "@elastic/eui";
import { DocViewRenderProps } from '../../doc_views/doc_views_types'; import { DocViewRenderProps } from "../../doc_views/doc_views_types";
export function JsonCodeBlock({ hit }: DocViewRenderProps) { 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 ( return (
<EuiCodeBlock aria-label={label} language="json" isCopyable paddingSize="s"> <EuiCodeBlock aria-label={label} language="json" isCopyable paddingSize="s">
{JSON.stringify(hit, null, 2)} {JSON.stringify(hit, null, 2)}

View File

@ -24,29 +24,34 @@ import {
EuiPopoverTitle, EuiPopoverTitle,
EuiSelectable, EuiSelectable,
EuiButtonEmptyProps, EuiButtonEmptyProps,
EuiTabs,
EuiTabbedContent,
EuiTab,
EuiSwitch,
} from '@elastic/eui'; } from '@elastic/eui';
import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable'; import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable';
import { IndexPatternRef } from './types'; import { IndexPatternRef } from './types';
export type ChangeIndexPatternTriggerProps = EuiButtonEmptyProps & { export type ChangeIndexPatternTriggerProps = EuiButtonEmptyProps & {
label: string; label: string;
title?: string; title?: string;
}; };
// TODO: refactor to shared component with ../../../../../../../../x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern
export function ChangeIndexPattern({ export function ChangeIndexPattern({
indexPatternRefs, indexPatternRefs,
indexPatternId, indexPatternId,
onChangeIndexPattern, onChangeIndexPattern,
trigger, trigger,
selectableProps, selectableProps,
indices,
}: { }: {
trigger: ChangeIndexPatternTriggerProps; trigger: ChangeIndexPatternTriggerProps;
indexPatternRefs: IndexPatternRef[]; indexPatternRefs: IndexPatternRef[];
onChangeIndexPattern: (newId: string) => void; onChangeIndexPattern: (newId: string, typ: string) => void;
indexPatternId?: string; indexPatternId?: string;
selectableProps?: EuiSelectableProps; selectableProps?: EuiSelectableProps;
indices: string[];
}) { }) {
const [isPopoverOpen, setPopoverIsOpen] = useState(false); const [isPopoverOpen, setPopoverIsOpen] = useState(false);
@ -68,22 +73,21 @@ export function ChangeIndexPattern({
); );
}; };
return ( const [selectedTabId, setSelectedTabId] = useState(indices.includes(indexPatternId) ? 1 :0);
<EuiPopover const onSelectedTabChanged = (id: number) => {
button={createTrigger()} setSelectedTabId(id);
isOpen={isPopoverOpen} };
closePopover={() => setPopoverIsOpen(false)} const [includeSystemIndex, setIncludeSystemIndex] = useState(false);
className="eui-textTruncate"
anchorClassName="eui-textTruncate" const tabs = React.useMemo(()=>{
display="block" const showIndices = includeSystemIndex ? indices: indices.filter(key=>!key.startsWith("."));
panelPaddingSize="s" const tabs = [
ownFocus {
> id: 'view',
<div style={{ width: 320 }}> name: 'View',
<EuiPopoverTitle> disabled: false,
content: ( <EuiSelectable
</EuiPopoverTitle> style={{marginTop:10}}
<EuiSelectable
data-test-subj="indexPattern-switcher" data-test-subj="indexPattern-switcher"
{...selectableProps} {...selectableProps}
searchable searchable
@ -98,7 +102,7 @@ export function ChangeIndexPattern({
const choice = (choices.find(({ checked }) => checked) as unknown) as { const choice = (choices.find(({ checked }) => checked) as unknown) as {
value: string; value: string;
}; };
onChangeIndexPattern(choice.value); onChangeIndexPattern(choice.value, 'view');
setPopoverIsOpen(false); setPopoverIsOpen(false);
}} }}
searchProps={{ searchProps={{
@ -112,7 +116,103 @@ export function ChangeIndexPattern({
{list} {list}
</> </>
)} )}
</EuiSelectable> </EuiSelectable>),
},
{
id: 'index',
name: 'Index',
disabled: false,
content:(
<div>
<div style={{display:'flex', margin:'10px auto', flexDirection: 'row-reverse',}}>
<EuiSwitch
label="Include system index"
checked={includeSystemIndex}
onChange={(e) => setIncludeSystemIndex(!includeSystemIndex)}
/>
</div>
<EuiSelectable
style={{marginTop:5}}
{...selectableProps}
searchable
singleSelection="always"
options={showIndices.map((indexName) => ({
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}
</>
)}
</EuiSelectable></div>),
},
];
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) => (
<EuiTab
key={index}
onClick={() => onSelectedTabChanged(tab.id)}
isSelected={tab.id === selectedTabId}
disabled={tab.disabled}
>
{tab.name}
</EuiTab>
));
};
return (
<EuiPopover
button={createTrigger()}
isOpen={isPopoverOpen}
closePopover={() => setPopoverIsOpen(false)}
className="eui-textTruncate"
anchorClassName="eui-textTruncate"
display="block"
panelPaddingSize="s"
ownFocus
>
<div style={{ width: 320 }}>
{/* <EuiPopoverTitle>
</EuiPopoverTitle> */}
{/* <EuiTabs size="s" expand>
{renderTabs()}
</EuiTabs> */}
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={tabs[selectedTabId]}
autoFocus="selected"
onTabClick={(tab) => {
const idx = tabs.findIndex(item=>item.id == tab.id);
setSelectedTabId(idx);
}}
/>
{/* <div style={{marginTop:5}}></div>
{selectedTabContent} */}
</div> </div>
</EuiPopover> </EuiPopover>
); );

View File

@ -16,15 +16,20 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React, { useState } from 'react'; import React, { useState } from "react";
import { EuiPopover, EuiPopoverTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import {
import { DiscoverFieldDetails } from './discover_field_details'; EuiPopover,
import { FieldIcon, FieldButton } from '../../../../../kibana_react/public'; EuiPopoverTitle,
import { FieldDetails } from './types'; EuiButtonIcon,
import { IndexPatternField, IndexPattern } from '../../../../../data/public'; EuiToolTip,
import { shortenDottedString } from '../../helpers'; } from "@elastic/eui";
import { getFieldTypeName } from './lib/get_field_type_name'; import { DiscoverFieldDetails } from "./discover_field_details";
import './discover_field.scss'; 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 { export interface DiscoverFieldProps {
/** /**
@ -42,7 +47,11 @@ export interface DiscoverFieldProps {
/** /**
* Callback to add a filter to filter bar * 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 * Callback to remove/deselect a the field
* @param fieldName * @param fieldName
@ -72,8 +81,8 @@ export function DiscoverField({
selected, selected,
useShortDots, useShortDots,
}: DiscoverFieldProps) { }: DiscoverFieldProps) {
const addLabelAria = `Add ${field.name } to table`; const addLabelAria = `Add ${field.name} to table`;
const removeLabelAria = `Remove ${field.name } to table`; const removeLabelAria = `Remove ${field.name} to table`;
const [infoIsOpen, setOpen] = useState(false); const [infoIsOpen, setOpen] = useState(false);
@ -93,11 +102,15 @@ export function DiscoverField({
// u200B is a non-width white-space character, which allows // u200B is a non-width white-space character, which allows
// the browser to efficiently word-wrap right after the dot // the browser to efficiently word-wrap right after the dot
// without us having to draw a lot of extra DOM elements, etc // 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 dscFieldIcon = (
<FieldIcon type={field.type} label={getFieldTypeName(field.type)} scripted={field.scripted} /> <FieldIcon
type={field.type}
label={getFieldTypeName(field.type)}
scripted={field.scripted}
/>
); );
const fieldName = ( const fieldName = (
@ -106,22 +119,21 @@ export function DiscoverField({
title={field.name} title={field.name}
className="dscSidebarField__name" className="dscSidebarField__name"
> >
{useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)} {useShortDots
? wrapOnDot(shortenDottedString(field.name))
: wrapOnDot(field.displayName)}
</span> </span>
); );
let actionButton; let actionButton;
if (field.name !== '_source' && !selected) { if (field.name !== "_source" && !selected) {
actionButton = ( actionButton = (
<EuiToolTip <EuiToolTip delay="long" content={"Add field as column"}>
delay="long"
content={ 'Add field as column'}
>
<EuiButtonIcon <EuiButtonIcon
iconType="plusInCircleFilled" iconType="plusInCircleFilled"
className="dscSidebarItem__action" className="dscSidebarItem__action"
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => { onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
if (ev.type === 'click') { if (ev.type === "click") {
ev.currentTarget.focus(); ev.currentTarget.focus();
} }
ev.preventDefault(); ev.preventDefault();
@ -133,18 +145,15 @@ export function DiscoverField({
/> />
</EuiToolTip> </EuiToolTip>
); );
} else if (field.name !== '_source' && selected) { } else if (field.name !== "_source" && selected) {
actionButton = ( actionButton = (
<EuiToolTip <EuiToolTip delay="long" content={"Remove field from table"}>
delay="long"
content={ 'Remove field from table'}
>
<EuiButtonIcon <EuiButtonIcon
color="danger" color="danger"
iconType="cross" iconType="cross"
className="dscSidebarItem__action" className="dscSidebarItem__action"
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => { onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
if (ev.type === 'click') { if (ev.type === "click") {
ev.currentTarget.focus(); ev.currentTarget.focus();
} }
ev.preventDefault(); ev.preventDefault();
@ -158,7 +167,7 @@ export function DiscoverField({
); );
} }
if (field.type === '_source') { if (field.type === "_source") {
return ( return (
<FieldButton <FieldButton
size="s" size="s"
@ -194,10 +203,7 @@ export function DiscoverField({
anchorPosition="rightUp" anchorPosition="rightUp"
panelClassName="dscSidebarItem__fieldPopoverPanel" panelClassName="dscSidebarItem__fieldPopoverPanel"
> >
<EuiPopoverTitle> <EuiPopoverTitle> {"Top 5 values"}</EuiPopoverTitle>
{' '}
{ 'Top 5 values'}
</EuiPopoverTitle>
{infoIsOpen && ( {infoIsOpen && (
<DiscoverFieldDetails <DiscoverFieldDetails
indexPattern={indexPattern} indexPattern={indexPattern}

View File

@ -34,7 +34,8 @@ export interface DiscoverIndexPatternProps {
/** /**
* triggered when user selects a new index pattern * triggered when user selects a new index pattern
*/ */
setIndexPattern: (id: string) => void; setIndexPattern: (id: string, typ: string) => void;
indices: string[];
} }
/** /**
@ -44,6 +45,7 @@ export function DiscoverIndexPattern({
indexPatternList, indexPatternList,
selectedIndexPattern, selectedIndexPattern,
setIndexPattern, setIndexPattern,
indices,
}: DiscoverIndexPatternProps) { }: DiscoverIndexPatternProps) {
const options: IndexPatternRef[] = (indexPatternList || []).map((entity) => ({ const options: IndexPatternRef[] = (indexPatternList || []).map((entity) => ({
id: entity.id, id: entity.id,
@ -75,13 +77,27 @@ export function DiscoverIndexPattern({
}} }}
indexPatternId={selected.id} indexPatternId={selected.id}
indexPatternRefs={options} indexPatternRefs={options}
onChangeIndexPattern={(id) => { onChangeIndexPattern={(id, typ) => {
const indexPattern = options.find((pattern) => pattern.id === id); 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) { if (indexPattern) {
setIndexPattern(id); setIndexPattern(id, typ);
setSelected(indexPattern); setSelected(indexPattern);
} }
}} }}
indices={indices}
/> />
</div> </div>
); );

View File

@ -1,4 +1,4 @@
@import '../../../../../core/public/variables.scss'; @import "../../../../../core/public/variables.scss";
.dscSidebar__container { .dscSidebar__container {
padding-left: 0 !important; padding-left: 0 !important;
padding-right: 0 !important; padding-right: 0 !important;
@ -26,11 +26,17 @@
.dscFieldListHeader { .dscFieldListHeader {
padding: $euiSizeS $euiSizeS 0 $euiSizeS; padding: $euiSizeS $euiSizeS 0 $euiSizeS;
background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade); background-color: lightOrDarkTheme(
tint($euiColorPrimary, 90%),
$euiColorLightShade
);
} }
.dscFieldList--popular { .dscFieldList--popular {
background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade); background-color: lightOrDarkTheme(
tint($euiColorPrimary, 90%),
$euiColorLightShade
);
} }
.dscFieldChooser { .dscFieldChooser {
@ -45,7 +51,7 @@
.dscSidebarItem { .dscSidebarItem {
&:hover, &:hover,
&:focus-within, &:focus-within,
&[class*='-isActive'] { &[class*="-isActive"] {
.dscSidebarItem__action { .dscSidebarItem__action {
opacity: 1; opacity: 1;
} }
@ -95,3 +101,23 @@
color: $euiTextColor; color: $euiTextColor;
margin-bottom: $euiSizeS; 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;
}
}
}
}

View File

@ -16,22 +16,27 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import './discover_sidebar.scss'; import "./discover_sidebar.scss";
import React, { useCallback, useEffect, useState, useMemo } from 'react'; import React, { useCallback, useEffect, useState, useMemo } from "react";
import { EuiButtonIcon, EuiTitle, EuiSpacer,EuiHideFor } from '@elastic/eui'; import { EuiButtonIcon, EuiTitle, EuiSpacer, EuiHideFor } from "@elastic/eui";
import { sortBy } from 'lodash'; import { sortBy } from "lodash";
import { DiscoverField } from './discover_field'; import { DiscoverField } from "./discover_field";
import { DiscoverIndexPattern } from './discover_index_pattern'; import { DiscoverIndexPattern } from "./discover_index_pattern";
import { DiscoverFieldSearch } from './discover_field_search'; import { DiscoverFieldSearch } from "./discover_field_search";
import { IndexPatternAttributes } from '../../../../../data/common'; import { IndexPatternAttributes } from "../../../../../data/common";
import { SavedObject } from '../../../../../../core/types'; import { SavedObject } from "../../../../../../core/types";
// import { FIELDS_LIMIT_SETTING } from '../../../../common'; // import { FIELDS_LIMIT_SETTING } from '../../../../common';
import { groupFields } from './lib/group_fields'; import { groupFields } from "./lib/group_fields";
import { IndexPatternField, IndexPattern, UI_SETTINGS } from '../../../../../data/public'; import {
import { getDetails } from './lib/get_details'; IndexPatternField,
import { getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter'; IndexPattern,
import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list'; 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 { getServices } from '../../../kibana_services';
import { Tree, Icon } from "antd";
export interface DiscoverSidebarProps { export interface DiscoverSidebarProps {
/** /**
@ -57,7 +62,11 @@ export interface DiscoverSidebarProps {
/** /**
* Callback function when adding a filter from sidebar * 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 * Callback function when removing a field
* @param fieldName * @param fieldName
@ -72,6 +81,7 @@ export interface DiscoverSidebarProps {
*/ */
setIndexPattern: (id: string) => void; setIndexPattern: (id: string) => void;
isClosed: boolean; isClosed: boolean;
indices: string[];
} }
export function DiscoverSidebar({ export function DiscoverSidebar({
@ -85,16 +95,22 @@ export function DiscoverSidebar({
selectedIndexPattern, selectedIndexPattern,
setIndexPattern, setIndexPattern,
isClosed, isClosed,
indices,
}: DiscoverSidebarProps) { }: DiscoverSidebarProps) {
const [showFields, setShowFields] = useState(false); const [showFields, setShowFields] = useState(false);
const [fields, setFields] = useState<IndexPatternField[] | null>(null); const [fields, setFields] = useState<IndexPatternField[] | null>(null);
const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter()); const [fieldFilterState, setFieldFilterState] = useState(
getDefaultFieldFilter()
);
// const services = useMemo(() => getServices(), []); // const services = useMemo(() => getServices(), []);
useEffect(() => { useEffect(() => {
const newFields = getIndexPatternFieldList(selectedIndexPattern, fieldCounts); const newFields = getIndexPatternFieldList(
selectedIndexPattern,
fieldCounts
);
setFields(newFields); setFields(newFields);
}, [selectedIndexPattern, fieldCounts, hits, ]);//services }, [selectedIndexPattern, fieldCounts, hits]); //services
const onChangeFieldSearch = useCallback( const onChangeFieldSearch = useCallback(
(field: string, value: string | boolean | undefined) => { (field: string, value: string | boolean | undefined) => {
@ -105,27 +121,51 @@ export function DiscoverSidebar({
); );
const getDetailsByField = useCallback( const getDetailsByField = useCallback(
(ipField: IndexPatternField) => getDetails(ipField, hits, columns, selectedIndexPattern), (ipField: IndexPatternField) =>
getDetails(ipField, hits, columns, selectedIndexPattern),
[hits, columns, selectedIndexPattern] [hits, columns, selectedIndexPattern]
); );
const popularLimit = 5;//services.uiSettings.get(FIELDS_LIMIT_SETTING); const popularLimit = 5; //services.uiSettings.get(FIELDS_LIMIT_SETTING);
const useShortDots = false;//services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const useShortDots = false; //services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE);
const { const {
selected: selectedFields, selected: selectedFields,
popular: popularFields, popular: popularFields,
unpopular: unpopularFields, unpopular: unpopularFields,
} = useMemo(() => groupFields(fields, columns, popularLimit, fieldCounts, fieldFilterState), [ fieldsTree,
} = useMemo(() => {
const groupedFields = groupFields(
fields, fields,
columns, columns,
popularLimit, popularLimit,
fieldCounts, fieldCounts,
fieldFilterState, 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 fieldTypes = useMemo(() => {
const result = ['any']; const result = ["any"];
if (Array.isArray(fields)) { if (Array.isArray(fields)) {
for (const field of fields) { for (const field of fields) {
if (result.indexOf(field.type) === -1) { if (result.indexOf(field.type) === -1) {
@ -136,20 +176,56 @@ export function DiscoverSidebar({
return result; return result;
}, [fields]); }, [fields]);
if (!selectedIndexPattern || !fields || isClosed ) { if (!selectedIndexPattern || !fields || isClosed) {
return null; return null;
} }
const buildTree = (treeObj: any) => {
return Object.keys(treeObj).map((key) => {
if (treeObj[key].isLeaf) {
return ( return (
<EuiHideFor sizes={['xs', 's']}> <Tree.TreeNode
<section icon={<Icon type="carry-out" />}
className="sidebar-list" selectable={false}
aria-label={'Index and fields'} title={
<DiscoverField
field={treeObj[key]}
indexPattern={selectedIndexPattern}
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
getDetails={getDetailsByField}
useShortDots={true}
/>
}
key={key}
/>
);
}
return (
<Tree.TreeNode
icon={<Icon type="carry-out" />}
selectable={false}
title={key}
key={key}
> >
{buildTree(treeObj[key])}
</Tree.TreeNode>
);
});
};
return (
<EuiHideFor sizes={["xs", "s"]}>
<section className="sidebar-list" aria-label={"Index and fields"}>
<DiscoverIndexPattern <DiscoverIndexPattern
selectedIndexPattern={selectedIndexPattern} selectedIndexPattern={selectedIndexPattern}
setIndexPattern={setIndexPattern} setIndexPattern={setIndexPattern}
indexPatternList={sortBy(indexPatternList, (o) => o.attributes.viewName)} indexPatternList={sortBy(
indexPatternList,
(o) => o.attributes.viewName
)}
indices={indices}
/> />
<div className="dscSidebar__item"> <div className="dscSidebar__item">
<form> <form>
@ -164,9 +240,7 @@ export function DiscoverSidebar({
{fields.length > 0 && ( {fields.length > 0 && (
<> <>
<EuiTitle size="xxxs" id="selected_fields"> <EuiTitle size="xxxs" id="selected_fields">
<h3> <h3>Selected fields</h3>
Selected fields
</h3>
</EuiTitle> </EuiTitle>
<EuiSpacer size="xs" /> <EuiSpacer size="xs" />
<ul <ul
@ -196,10 +270,12 @@ export function DiscoverSidebar({
})} })}
</ul> </ul>
<div className="euiFlexGroup euiFlexGroup--gutterMedium"> <div className="euiFlexGroup euiFlexGroup--gutterMedium">
<EuiTitle size="xxxs" id="available_fields" className="euiFlexItem"> <EuiTitle
<h3> size="xxxs"
Available fields id="available_fields"
</h3> className="euiFlexItem"
>
<h3>Available fields</h3>
</EuiTitle> </EuiTitle>
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero"> {/* <div className="euiFlexItem euiFlexItem--flexGrowZero">
<EuiButtonIcon <EuiButtonIcon
@ -220,15 +296,20 @@ export function DiscoverSidebar({
<div> <div>
<EuiTitle <EuiTitle
size="xxxs" size="xxxs"
className={`dscFieldListHeader ${!showFields ? 'hidden-sm hidden-xs' : ''}`} className={`dscFieldListHeader ${
!showFields ? "hidden-sm hidden-xs" : ""
}`}
>
<h4
style={{ fontWeight: "normal" }}
id="available_fields_popular"
> >
<h4 style={{ fontWeight: 'normal' }} id="available_fields_popular">
Popular Popular
</h4> </h4>
</EuiTitle> </EuiTitle>
<ul <ul
className={`dscFieldList dscFieldList--popular ${ className={`dscFieldList dscFieldList--popular ${
!showFields ? 'hidden-sm hidden-xs' : '' !showFields ? "hidden-sm hidden-xs" : ""
}`} }`}
aria-labelledby="available_fields available_fields_popular" aria-labelledby="available_fields available_fields_popular"
data-test-subj={`fieldList-popular`} data-test-subj={`fieldList-popular`}
@ -256,9 +337,9 @@ export function DiscoverSidebar({
</div> </div>
)} )}
<ul {/* <ul
className={`dscFieldList dscFieldList--unpopular ${ className={`dscFieldList dscFieldList--unpopular ${
!showFields ? 'hidden-sm hidden-xs' : '' !showFields ? "hidden-sm hidden-xs" : ""
}`} }`}
aria-labelledby="available_fields" aria-labelledby="available_fields"
data-test-subj={`fieldList-unpopular`} data-test-subj={`fieldList-unpopular`}
@ -282,7 +363,16 @@ export function DiscoverSidebar({
</li> </li>
); );
})} })}
</ul> </ul> */}
<div id="fields-tree-wrapper">
<Tree
showLine={false}
showIcon={false}
defaultExpandedKeys={["0-0-0", "0-0-1", "0-0-2"]}
>
{buildTree(fieldsTree)}
</Tree>
</div>
</div> </div>
</section> </section>
</EuiHideFor> </EuiHideFor>

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from "react";
import { import {
EuiFlexGroup, EuiFlexGroup,
EuiFlexItem, EuiFlexItem,
@ -24,8 +24,8 @@ import {
EuiText, EuiText,
EuiSelect, EuiSelect,
EuiIconTip, EuiIconTip,
} from '@elastic/eui'; } from "@elastic/eui";
import moment from 'moment'; import moment from "moment";
export interface TimechartHeaderProps { export interface TimechartHeaderProps {
/** /**
@ -39,6 +39,7 @@ export interface TimechartHeaderProps {
scaled?: boolean; scaled?: boolean;
description?: string; description?: string;
scale?: number; scale?: number;
timeFieldName: string;
}; };
/** /**
* Range of dates to be displayed * Range of dates to be displayed
@ -59,6 +60,7 @@ export interface TimechartHeaderProps {
* selected interval * selected interval
*/ */
stateInterval: string; stateInterval: string;
hits: number;
} }
export function TimechartHeader({ export function TimechartHeader({
@ -68,12 +70,13 @@ export function TimechartHeader({
options, options,
onChangeInterval, onChangeInterval,
stateInterval, stateInterval,
hits,
}: TimechartHeaderProps) { }: TimechartHeaderProps) {
const [interval, setInterval] = useState(stateInterval); const [interval, setInterval] = useState(stateInterval);
const toMoment = useCallback( const toMoment = useCallback(
(datetime: string) => { (datetime: string) => {
if (!datetime) { if (!datetime) {
return ''; return "";
} }
if (!dateFormat) { if (!dateFormat) {
return datetime; return datetime;
@ -92,27 +95,36 @@ export function TimechartHeader({
onChangeInterval(e.target.value); onChangeInterval(e.target.value);
}; };
if (!timeRange || !bucketInterval) { // if (!timeRange || !bucketInterval) {
return null; // return null;
} // }
return ( return (
<EuiFlexGroup gutterSize="s" responsive justifyContent="center" alignItems="center"> <EuiFlexGroup
<EuiFlexItem grow={false}> gutterSize="s"
<EuiToolTip responsive
content={ 'To change the time, use the global time filter above'} justifyContent="center"
delay="long" alignItems="center"
> >
<EuiText data-test-subj="discoverIntervalDateRange" size="s">
{`${toMoment(timeRange.from)} - ${toMoment(timeRange.to)} ${
interval !== 'auto'
? 'per'
: ''
}`}
</EuiText>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<div>
Found <span style={{ fontSize: 16, fontWeight: "bold" }}>{hits}</span>{" "}
records
{timeRange && (
<span style={{ fontSize: 12, marginLeft: 5 }}>
{`between ${toMoment(timeRange.from)} and ${toMoment(
timeRange.to
)} ${
interval !== "auto" ? "" : "" //per
}`}
</span>
)}
{bucketInterval && (
<span>{`(${bucketInterval.timeFieldName} per ${bucketInterval.description})`}</span>
)}
</div>
</EuiFlexItem>
{/* <EuiFlexItem grow={false}>
<EuiSelect <EuiSelect
aria-label={'Time interval'} aria-label={'Time interval'}
compressed compressed
@ -145,7 +157,7 @@ export function TimechartHeader({
) : undefined ) : undefined
} }
/> />
</EuiFlexItem> </EuiFlexItem> */}
</EuiFlexGroup> </EuiFlexGroup>
); );
} }

View File

@ -23,4 +23,5 @@ const DOT_PREFIX_RE = /(.).+?\./g;
* Convert a dot.notated.string into a short * Convert a dot.notated.string into a short
* version (d.n.string) * 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.');

View File

@ -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];
}

View File

@ -101,6 +101,7 @@ export default {
type: 'saveData', type: 'saveData',
payload: { payload: {
clusterList: newClusterList, clusterList: newClusterList,
clusterTotal: res.total,
search: { search: {
...search, ...search,
cluster: payload, cluster: payload,

View File

@ -1,9 +1,13 @@
import * as React from 'react'; import * as React from 'react';
import {Tabs} from 'antd'; import {Tabs, Row, Col, Card} from 'antd';
import Clusters from './components/clusters'; import Clusters from './components/clusters';
import styles from "./Overview.less";
import {connect} from "dva";
import {formatter} from '@/lib/format';
const {TabPane} = Tabs; const {TabPane} = Tabs;
const panes = [ const panes = [
{ title: 'Clusters', component: Clusters, key: 'clusters' }, { title: 'Clusters', component: Clusters, key: 'clusters' },
{ title: 'Hosts', component: 'Content of Tab 2', key: 'hosts' }, { title: 'Hosts', component: 'Content of Tab 2', key: 'hosts' },
@ -11,9 +15,66 @@ const panes = [
{title: 'Indices', component: 'Content of Tab 3',key: 'indices'}, {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 (<div style={{background:'#fff'}} className="overview"> return (<div style={{background:'#fff'}} className="overview">
<div>
<Row gutter={24} className={styles.rowSpace}>
<Col md={6} sm={12}>
<Card
bodyStyle={{ paddingBottom: 20 }}
className={styles.clusterMeta}
>
<Card.Meta title='集群总数' className={styles.title} />
<div>
<span className={styles.total}>{clusterTotal?.value}</span>
</div>
</Card>
</Col>
<Col md={6} sm={12}>
<Card
bodyStyle={{ paddingBottom: 20 }}
className={styles.clusterMeta}
>
<Card.Meta title='主机总数' className={styles.title} />
<div>
<span className={styles.total}>{overview?.total_host}</span>
</div>
</Card>
</Col>
<Col md={6} sm={12}>
<Card
bodyStyle={{ paddingBottom: 20 }}
className={styles.clusterMeta}
>
<Card.Meta title='节点总数' className={styles.title} />
<div>
<span className={styles.total}>{overview?.total_node}</span>
</div>
</Card>
</Col>
<Col md={6} sm={12}>
<Card
bodyStyle={{ paddingBottom: 20 }}
className={styles.clusterMeta}
>
<Card.Meta title='存储空间' className={styles.title} />
<div>
<span className={styles.total}>{totalStoreSize.size || '-'}</span><span className={styles.unit}>{totalStoreSize.unit}</span>
</div>
</Card>
</Col>
</Row>
</div>
<div> <div>
<Tabs <Tabs
onChange={()=>{}} onChange={()=>{}}
@ -31,4 +92,10 @@ const NewOverview = ()=>{
</div>); </div>);
} }
export default NewOverview; export default connect(({
clusterOverview,
global
})=>({
overview: clusterOverview.overview,
clusterTotal: global.clusterTotal,
}))(NewOverview)

View File

@ -7,8 +7,8 @@ import React, {
createContext, createContext,
useContext, useContext,
useRef, useRef,
} from 'react'; } from "react";
import classNames from 'classnames'; import classNames from "classnames";
import { import {
EuiDataGrid, EuiDataGrid,
@ -25,56 +25,71 @@ import {
EuiPage, EuiPage,
EuiPageBody, EuiPageBody,
EuiPageContent, EuiPageContent,
} from '@elastic/eui'; EuiSelect,
import '@elastic/eui/dist/eui_theme_amsterdam_light.css'; } from "@elastic/eui";
import * as styles from './discover.scss'; import "@elastic/eui/dist/eui_theme_amsterdam_light.css";
import { Subscription } from 'rxjs'; import * as styles from "./discover.scss";
import { connect } from 'dva'; 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 DiscoverGrid from './Components/discover_grid';
import {flattenHitWrapper} from '../../components/kibana/data/common/index_patterns/index_patterns'; 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 { 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 { DiscoverSidebar } from "../../components/kibana/discover/public/application/components/sidebar/discover_sidebar";
import {HitsCounter } from '../../components/kibana/discover/public/application/components/hits_counter'; import { HitsCounter } from "../../components/kibana/discover/public/application/components/hits_counter";
import {TimechartHeader } from '../../components/kibana/discover/public/application/components/timechart_header'; import { TimechartHeader } from "../../components/kibana/discover/public/application/components/timechart_header";
import {DiscoverHistogram} from '../../components/kibana/discover/public/application/components/histogram/histogram'; import { DiscoverHistogram } from "../../components/kibana/discover/public/application/components/histogram/histogram";
import moment from 'moment'; import moment from "moment";
import {getContext} from './context'; import { getContext } from "./context";
import {createSearchBar} from '../../components/kibana/data/public/ui/search_bar/create_search_bar'; 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 { 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 { DiscoverNoResults } from "../../components/kibana/discover/public/application/angular/directives/no_results";
import { buildPointSeriesData } from '../../components/kibana/discover/public/application/angular/helpers/'; import { buildPointSeriesData } from "../../components/kibana/discover/public/application/angular/helpers/";
import {generateFilters} from '../../components/kibana/data/public/query/filter_manager/lib/generate_filters'; 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 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 {
import {Route} from 'umi'; useQueryParam,
import {ESPrefix} from '@/services/common'; StringParam,
QueryParamProvider,
ArrayParam,
} from "use-query-params";
import { Route } from "umi";
import { ESPrefix } from "@/services/common";
const SidebarMemoized = React.memo(DiscoverSidebar); const SidebarMemoized = React.memo(DiscoverSidebar);
const {filterManager, queryStringManager, timefilter, storage, getEsQuery, getSearchParams, const {
intervalOptions, getTimeBuckets, fetchESRequest, services} = getContext(); filterManager,
queryStringManager,
timefilter,
storage,
getEsQuery,
getSearchParams,
intervalOptions,
getTimeBuckets,
fetchESRequest,
services,
} = getContext();
const SearchBar = createSearchBar(); const SearchBar = createSearchBar();
const Discover = (props) => {
const Discover = (props)=>{ const [columnsParam, setColumnsParam] = useQueryParam("columns", ArrayParam);
const [queryParam, setQueryParam] = useQueryParam("query", StringParam);
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({ const [state, setState] = useState({
columns: columnsParam||['_source'],//['name', 'address'], columns: columnsParam || ["_source"], //['name', 'address'],
interval: 'auto', interval: "auto",
}); });
// const [sort, setSort] = useState(null); // const [sort, setSort] = useState(null);
const subscriptions = useMemo(()=>{ const subscriptions = useMemo(() => {
const subscriptions = new Subscription(); const subscriptions = new Subscription();
subscriptions.add( subscriptions.add(
timefilter.getAutoRefreshFetch$().subscribe({ timefilter.getAutoRefreshFetch$().subscribe({
@ -84,28 +99,33 @@ const Discover = (props)=>{
}) })
); );
return subscriptions; return subscriptions;
},[props.indexPattern]); }, [props.indexPattern]);
const setIndexPattern = async (id)=>{ const setIndexPattern = async (id, typ) => {
const IP = await services.indexPatternService.get(id); const IP = await services.indexPatternService.get(id, typ);
subscriptions.unsubscribe(); subscriptions.unsubscribe();
props.changeIndexPattern(IP); props.changeIndexPattern(IP);
setState({ setState({
...state, ...state,
columns: ['_source'], columns: ["_source"],
sort: [], 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 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 indexPattern = props.indexPattern;
const indexPatterns = [indexPattern]; const indexPatterns = [indexPattern];
const indexPatternList = props.indexPatternList const indexPatternList = props.indexPatternList;
const contentCentered = false; //resultState != 'ready'; const contentCentered = false; //resultState != 'ready';
indexPatterns.get = (id)=>{ indexPatterns.get = (id) => {
return Promise.resolve(indexPatterns.find(ip=>ip.id==id)); return Promise.resolve(indexPatterns.find((ip) => ip.id == id));
} };
getContext().setIndexPatterns(indexPatterns) getContext().setIndexPatterns(indexPatterns);
const [expandedDoc, setExpandedDoc] = useState(undefined); const [expandedDoc, setExpandedDoc] = useState(undefined);
const scrollableDesktop = useRef(null); const scrollableDesktop = useRef(null);
@ -114,18 +134,22 @@ const Discover = (props)=>{
const updateQuery = useCallback( const updateQuery = useCallback(
async (_payload) => { async (_payload) => {
if(!indexPattern){ if (!indexPattern) {
return return;
} }
setResultState('loading'); setResultState("loading");
const params = getSearchParams(_payload?.indexPattern || indexPattern, _payload?.interval || state.interval, _payload?.sort); const params = getSearchParams(
const res = await fetchESRequest(params, props.selectedCluster.id) _payload?.indexPattern || indexPattern,
if(!res.hits.hits){ _payload?.interval || state.interval,
res.hits.hits=[]; _payload?.sort
);
const res = await fetchESRequest(params, props.selectedCluster.id);
if (!res.hits.hits) {
res.hits.hits = [];
} }
setSearchRes(res); setSearchRes(res);
const {query} = queryStringManager.getQuery(); const { query } = queryStringManager.getQuery();
if(query != queryParam){ if (query != queryParam) {
setQueryParam(query); setQueryParam(query);
} }
// let filters = filterManager.getFilters(); // let filters = filterManager.getFilters();
@ -135,54 +159,66 @@ const Discover = (props)=>{
// console.log(filters) // console.log(filters)
// console.log(res) // console.log(res)
//console.log(JSON.stringify(params)); //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( const onChangeInterval = useCallback(
(interval) => { (interval) => {
if (interval) { if (interval) {
//console.log(calculateInterval(interval)) //console.log(calculateInterval(interval))
setState({...state, interval }); setState({ ...state, interval });
} }
}, },
[setState, indexPattern] [setState, indexPattern]
); );
const [searchRes, setSearchRes] = useState( 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":[]}}} 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 [resultState, setResultState] = useState('loading'); const { histogramData, timeChartProps } = useMemo(() => {
const {histogramData, timeChartProps} = useMemo(()=>{ if (!searchRes.hits.hits || searchRes.hits.hits.length == 0) {
if(!searchRes.hits.hits || searchRes.hits.hits.length == 0){ setResultState("none");
setResultState('none');
return {}; return {};
} }
if(!indexPattern.timeFieldName || !searchRes.aggregations){ if (!indexPattern.timeFieldName || !searchRes.aggregations) {
setResultState('ready'); setResultState("ready");
return {histogramData:null, timeChartProps:null} return { histogramData: null, timeChartProps: null };
} }
const buckets = getTimeBuckets(state.interval); const buckets = getTimeBuckets(state.interval);
const interval = buckets.getInterval(true); const interval = buckets.getInterval(true);
const chartTable = { 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: [], rows: [],
}; };
let aggregations = searchRes.aggregations; let aggregations = searchRes.aggregations;
aggregations["2"].buckets.forEach((bk)=>{ aggregations["2"].buckets.forEach((bk) => {
chartTable.rows.push(bk); chartTable.rows.push(bk);
}) });
//console.log(interval, moment.duration('1', 'd')) //console.log(interval, moment.duration('1', 'd'))
const dimensions = { const dimensions = {
x: { x: {
accessor: 0, accessor: 0,
format: { format: {
id: 'date', id: "date",
params: { params: {
pattern: buckets.getScaledDateFormat(), pattern: buckets.getScaledDateFormat(),
//pattern: 'YYYY-MM-DD', //pattern: 'YYYY-MM-DD',
@ -199,12 +235,12 @@ const Discover = (props)=>{
y: { y: {
accessor: 1, accessor: 1,
format: { format: {
id: 'number', id: "number",
},
label: "Count",
}, },
label: 'Count',
}
}; };
setResultState('ready'); setResultState("ready");
const timeChartProps = { const timeChartProps = {
timeRange: { timeRange: {
from: timefilter.getBounds().min, from: timefilter.getBounds().min,
@ -217,30 +253,35 @@ const Discover = (props)=>{
// scaled: true, // scaled: true,
// description: 'day', // description: 'day',
// scale: undefined, // scale: undefined,
...interval ...interval,
timeFieldName: indexPattern.timeFieldName,
}, },
} };
const histogramData = buildPointSeriesData(chartTable, dimensions); const histogramData = buildPointSeriesData(chartTable, dimensions);
return {histogramData, timeChartProps} return { histogramData, timeChartProps };
}, [searchRes, indexPattern]) }, [searchRes, indexPattern, indexPattern.timeFieldName]);
const setAppState = (newState)=>{ const setAppState = (newState) => {
if(state.columns[0] === '_source' || newState.columns[0] === '_source' || (state.columns.length != newState.columns.length)){ if (
state.columns[0] === "_source" ||
newState.columns[0] === "_source" ||
state.columns.length != newState.columns.length
) {
setColumnsParam(newState.columns); setColumnsParam(newState.columns);
} }
setState(newState) setState(newState);
} };
const { onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useMemo( const { onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useMemo(
() => () =>
getStateColumnActions({ getStateColumnActions({
indexPattern, indexPattern,
indexPatterns:[indexPattern], indexPatterns: [indexPattern],
setAppState: setAppState, setAppState: setAppState,
state, state,
useNewFieldsApi:false, useNewFieldsApi: false,
}), }),
[indexPattern,state] [indexPattern, state]
); );
const collapseIcon = useRef(null); const collapseIcon = useRef(null);
@ -250,18 +291,19 @@ const Discover = (props)=>{
}, [state]); }, [state]);
const hideChart = useMemo(() => state.hideChart, [state]); const hideChart = useMemo(() => state.hideChart, [state]);
const [isSidebarClosed, setIsSidebarClosed] = useState(false); const [isSidebarClosed, setIsSidebarClosed] = useState(false);
const useNewFieldsApi = false const useNewFieldsApi = false;
const onSort = useCallback( const onSort = useCallback(
(nsort) => { (nsort) => {
setState({...state, sort: nsort}) setState({ ...state, sort: nsort });
updateQuery({sort: nsort}); updateQuery({ sort: nsort });
},[state, indexPattern] },
[state, indexPattern]
); );
const onAddFilter = useCallback( const onAddFilter = useCallback(
(field, values, operation) => { (field, values, operation) => {
const fieldName = typeof field === 'string' ? field : field.name; const fieldName = typeof field === "string" ? field : field.name;
const newFilters = generateFilters( const newFilters = generateFilters(
filterManager, filterManager,
field, field,
@ -270,7 +312,7 @@ const Discover = (props)=>{
String(indexPattern.id) String(indexPattern.id)
); );
filterManager.addFilters(newFilters); filterManager.addFilters(newFilters);
updateQuery() updateQuery();
}, },
[indexPattern] [indexPattern]
); );
@ -281,16 +323,15 @@ const Discover = (props)=>{
timefilter.setTime({ timefilter.setTime({
from: moment(ranges.from).toISOString(), from: moment(ranges.from).toISOString(),
to: moment(ranges.to).toISOString(), to: moment(ranges.to).toISOString(),
mode: 'absolute', mode: "absolute",
}); });
}, },
[timefilter] [timefilter]
); );
const rows = searchRes.hits.hits; const rows = searchRes.hits.hits;
const opts = { const opts = {
savedSearch:{}, savedSearch: {},
timefield: indexPattern?.getTimeField()?.displayName, timefield: indexPattern?.getTimeField()?.displayName,
chartAggConfigs: {}, chartAggConfigs: {},
}; };
@ -301,47 +342,61 @@ const Discover = (props)=>{
} }
} }
const hits = searchRes.hits.total?.value || searchRes.hits.total; const hits = searchRes.hits.total?.value || searchRes.hits.total;
const resetQuery = ()=>{}; const resetQuery = () => {};
const showDatePicker = indexPattern.timeFieldName != ""; const showDatePicker = indexPattern.timeFieldName != "";
const saveDocument = useCallback(async ({_index, _id, _type, _source, is_new})=>{ const saveDocument = useCallback(
const {http} = getContext(); async ({ _index, _id, _type, _source, is_new }) => {
const res = await http.put(`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, { const { http } = getContext();
const res = await http.put(
`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`,
{
prependBasePath: false, prependBasePath: false,
query: { query: {
_type, _type,
is_new, is_new,
}, },
body: JSON.stringify(_source), body: JSON.stringify(_source),
});
if(res.error){
message.error(res.error)
return res
} }
message.success('saved successfully'); );
updateQuery() if (res.error) {
message.error(res.error);
return res; return res;
},[props.selectedCluster, updateQuery]) }
message.success("saved successfully");
updateQuery();
return res;
},
[props.selectedCluster, updateQuery]
);
const deleteDocument = useCallback(async ({_index, _id, _type})=>{ const deleteDocument = useCallback(
const {http} = getContext(); async ({ _index, _id, _type }) => {
const res = await http.delete(`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`, { const { http } = getContext();
const res = await http.delete(
`/elasticsearch/${props.selectedCluster.id}/doc/${_index}/${_id}`,
{
prependBasePath: false, prependBasePath: false,
query: { query: {
_type, _type,
},
} }
}); );
if(res.error){ if (res.error) {
message.error(res.error) message.error(res.error);
return res return res;
} }
message.success('deleted successfully'); message.success("deleted successfully");
updateQuery() updateQuery();
return res return res;
},[props.selectedCluster, updateQuery]) },
[props.selectedCluster, updateQuery]
);
const [settingsVisible, setSettingsVisible] = React.useState(false);
return ( return (
<Card bordered={false}> <Card bordered={false} bodyStyle={{ paddingTop: 10 }}>
<SearchBar <SearchBar
{...{ {...{
showSearchBar: false, showSearchBar: false,
@ -350,14 +405,14 @@ const Discover = (props)=>{
showDatePicker: showDatePicker, showDatePicker: showDatePicker,
showFilterBar: true, showFilterBar: true,
useDefaultBehaviors: true, useDefaultBehaviors: true,
screenTitle: '', screenTitle: "",
// filters: filters, // filters: filters,
onFiltersUpdated: getContext().defaultFiltersUpdated(), onFiltersUpdated: getContext().defaultFiltersUpdated(),
indexPatterns: [indexPattern], indexPatterns: [indexPattern],
filterManager, filterManager,
query: { query: {
language: 'kuery', language: "kuery",
query: queryParam || '', query: queryParam || "",
}, },
queryStringManager, queryStringManager,
queryString: queryStringManager, queryString: queryStringManager,
@ -368,9 +423,10 @@ const Discover = (props)=>{
}} }}
/> />
<EuiPageBody className="dscPageBody" aria-describedby="savedSearchTitle"> <EuiPageBody className="dscPageBody" aria-describedby="savedSearchTitle">
<EuiFlexGroup className="dscPageBody__contents" gutterSize="none"> <EuiFlexGroup className="dscPageBody__contents" gutterSize="none">
{indexPattern && <EuiFlexItem grow={false}> {indexPattern && (
<EuiFlexItem grow={false}>
<SidebarMemoized <SidebarMemoized
config={{}} config={{}}
columns={columns} columns={columns}
@ -388,48 +444,82 @@ const Discover = (props)=>{
isClosed={isSidebarClosed} isClosed={isSidebarClosed}
//unmappedFieldsConfig={unmappedFieldsConfig} //unmappedFieldsConfig={unmappedFieldsConfig}
//useNewFieldsApi={useNewFieldsApi} //useNewFieldsApi={useNewFieldsApi}
indices={props.indices}
/> />
</EuiFlexItem>} </EuiFlexItem>
<EuiHideFor sizes={['xs', 's']}> )}
<EuiHideFor sizes={["xs", "s"]}>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiButtonIcon <EuiButtonIcon
iconType={isSidebarClosed ? 'menuRight' : 'menuLeft'} iconType={isSidebarClosed ? "menuRight" : "menuLeft"}
iconSize="m" iconSize="m"
size="s" size="s"
onClick={() => setIsSidebarClosed(!isSidebarClosed)} onClick={() => {
setIsSidebarClosed(!isSidebarClosed);
setTimeout(() => {
window.dispatchEvent(new Event("resize"));
}, 50);
}}
data-test-subj="collapseSideBarButton" data-test-subj="collapseSideBarButton"
aria-controls="discover-sidebar" aria-controls="discover-sidebar"
aria-expanded={isSidebarClosed ? 'false' : 'true'} aria-expanded={isSidebarClosed ? "false" : "true"}
aria-label={'Toggle sidebar'} aria-label={"Toggle sidebar"}
buttonRef={collapseIcon} buttonRef={collapseIcon}
/> />
</EuiFlexItem> </EuiFlexItem>
</EuiHideFor> </EuiHideFor>
{resultState === 'none' && ( {resultState === "none" && (
<DiscoverNoResults <DiscoverNoResults
timeFieldName={opts.timefield} timeFieldName={opts.timefield}
queryLanguage={state.query?.language || ''} queryLanguage={state.query?.language || ""}
// data={opts.data} // data={opts.data}
// error={fetchError} // error={fetchError}
/> />
)} )}
{resultState !== 'none' && ( {resultState !== "none" && (
<EuiFlexItem className="dscPageContent__wrapper" style={{overflow:'hidden'}}> <EuiFlexItem
className="dscPageContent__wrapper"
style={{ overflow: "hidden" }}
>
<EuiPageContent <EuiPageContent
// verticalPosition={resultState == 'loading' ? 'center' : undefined} // verticalPosition={resultState == 'loading' ? 'center' : undefined}
// horizontalPosition={resultState == 'loading' ? 'center' : undefined} // horizontalPosition={resultState == 'loading' ? 'center' : undefined}
paddingSize="none" paddingSize="none"
className={classNames('dscPageContent', { className={classNames("dscPageContent", {
'dscPageContent--centered': contentCentered, "dscPageContent--centered": contentCentered,
})} })}
> >
<div style={{ display: resultState !== 'loading' ? 'none' : '' }}> {/* todo add settings icon */}
{props.timeFields.length > 0 && (
<div
className="dscSetting setting-icon"
onClick={() => {
setSettingsVisible(!settingsVisible);
}}
>
<Icon type="setting" />
</div>
)}
{settingsVisible ? (
<SettingContent
{...timeChartProps}
indexPattern={indexPattern}
timeFields={props.timeFields}
onVisibleChange={(visible) => {
setSettingsVisible(visible);
}}
onTimeFieldChange={onTimeFieldChange}
/>
) : null}
<div
style={{ display: resultState !== "loading" ? "none" : "" }}
>
<div className="dscOverlay"> <div className="dscOverlay">
<LoadingSpinner /> <LoadingSpinner />
</div> </div>
</div> </div>
{/* {resultState === 'loading' && <LoadingSpinner />} */} {/* {resultState === 'loading' && <LoadingSpinner />} */}
{ ( {
<EuiFlexGroup <EuiFlexGroup
className="dscPageContent__inner" className="dscPageContent__inner"
direction="column" direction="column"
@ -437,45 +527,73 @@ const Discover = (props)=>{
gutterSize="none" gutterSize="none"
responsive={false} responsive={false}
> >
{hits > 0 && <EuiFlexItem grow={false} className="dscResultCount"> {hits > 0 && (
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween"> <EuiFlexItem grow={false} className="dscResultCount">
{/* <EuiFlexGroup
alignItems="center"
justifyContent="spaceBetween"
>
<EuiFlexItem <EuiFlexItem
grow={false} grow={false}
className="dscResuntCount__title eui-textTruncate eui-textNoWrap" className="dscResuntCount__title eui-textTruncate eui-textNoWrap"
style={{width:'100%'}} style={{ width: "100%" }}
> >
<HitsCounter <HitsCounter
hits={hits > 0 ? hits : 0} hits={hits > 0 ? hits : 0}
showResetButton={!!(opts.savedSearch && opts.savedSearch.id)} showResetButton={
!!(opts.savedSearch && opts.savedSearch.id)
}
onResetQuery={resetQuery} onResetQuery={resetQuery}
/> />
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup> */}
<EuiFlexGroup alignItems="center" style={{marginBottom:5}} justifyContent="spaceBetween">
{!hideChart && ( <div
<EuiFlexItem className="dscResultCount__actions"> style={{
marginTop: 10,
marginBottom: 10,
textAlign: "center",
}}
>
<TimechartHeader <TimechartHeader
dateFormat={'YYYY-MM-DD H:mm:ss'} hits={hits > 0 ? hits : 0}
dateFormat={"YYYY-MM-DD H:mm"}
{...timeChartProps} {...timeChartProps}
/> />
</div>
{props.timeFields.length > 0 &&
indexPattern.timeFieldName == "" && (
<div className="fake-chart">
<div className="fake-mask">
Click the button of right corner to select a
time field
</div>
<img
src={TimeFieldExampleImage}
style={{ width: "100%" }}
/>
</div>
)}
</EuiFlexItem> </EuiFlexItem>
)} )}
</EuiFlexGroup>
</EuiFlexItem>}
{!hideChart && opts.timefield && ( {!hideChart && opts.timefield && (
<EuiFlexItem> <EuiFlexItem>
<section <section
aria-label={'Histogram of found documents'} aria-label={"Histogram of found documents"}
className="dscTimechart" className="dscTimechart"
> >
{opts.chartAggConfigs && histogramData && rows.length !== 0 && ( {opts.chartAggConfigs &&
histogramData &&
rows.length !== 0 && (
<div <div
className='dscHistogramGrid' className="dscHistogramGrid"
data-test-subj="discoverChart" data-test-subj="discoverChart"
> >
<DiscoverHistogram <DiscoverHistogram
chartData={histogramData} chartData={histogramData}
timefilterUpdateHandler={timefilterUpdateHandler} timefilterUpdateHandler={
timefilterUpdateHandler
}
/> />
</div> </div>
)} )}
@ -491,131 +609,187 @@ const Discover = (props)=>{
ref={scrollableDesktop} ref={scrollableDesktop}
tabIndex={-1} tabIndex={-1}
> >
<h2 className="euiScreenReaderOnly" id="documentsAriaLabel"> <h2
className="euiScreenReaderOnly"
id="documentsAriaLabel"
>
Documents Documents
</h2> </h2>
{(rows && rows.length > 0) ? ( {rows && rows.length > 0 ? (
<div className="dscDiscoverGrid"> <div className="dscDiscoverGrid">
{/* <DiscoverGrid <Table
ariaLabelledBy="documentsAriaLabel"
columns={columns} columns={columns}
expandedDoc={expandedDoc} sortOrder={state.sort || []}
indexPattern={indexPattern}
rows={rows}
sort={(state.sort) || []}
sampleSize={opts.sampleSize}
searchDescription={opts.savedSearch.description}
searchTitle={opts.savedSearch.lastSavedTitle}
setExpandedDoc={setExpandedDoc}
showTimeCol={
!!indexPattern.timeFieldName
}
settings={state.grid}
onAddColumn={onAddColumn}
onFilter={onAddFilter}
onRemoveColumn={onRemoveColumn}
onSetColumns={onSetColumns}
onSort={onSort}
useNewFieldsApi={false}
/> */}
<Table columns={columns}
sortOrder={state.sort ||[]}
indexPattern={indexPattern} indexPattern={indexPattern}
onFilter={onAddFilter} onFilter={onAddFilter}
onRemoveColumn={onRemoveColumn} onRemoveColumn={onRemoveColumn}
onMoveColumn={onMoveColumn} onMoveColumn={onMoveColumn}
onAddColumn={onAddColumn} onAddColumn={onAddColumn}
onChangeSortOrder={onSort} onChangeSortOrder={onSort}
document={{saveDocument, deleteDocument}} document={{ saveDocument, deleteDocument }}
hits={rows}/> hits={rows}
/>
</div> </div>
):null} ) : null}
</section> </section>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
)} }
</EuiPageContent> </EuiPageContent>
</EuiFlexItem>)} </EuiFlexItem>
)}
</EuiFlexGroup> </EuiFlexGroup>
</EuiPageBody> </EuiPageBody>
</Card> </Card>
); );
} };
const DiscoverUI = (props)=>{ const DiscoverUI = (props) => {
const [viewID, setViewID] = useQueryParam('viewID', StringParam); const [viewID, setViewID] = useQueryParam("viewID", StringParam);
const [queryParam, setQueryParam] = useQueryParam('query', StringParam); const [index, setIndex] = useQueryParam("index", StringParam);
// const [type, setType] = useQueryParam('type', StringParam);
const [queryParam, setQueryParam] = useQueryParam("query", StringParam);
const [state, setState] = useState({ const [state, setState] = useState({
indices: [],
timeFields: [],
}); });
useMemo(()=>{ useMemo(() => {
const {http} = getContext(); const { http } = getContext();
http.getServerBasePath = ()=>{ http.getServerBasePath = () => {
return `${ESPrefix}/`+ props.selectedCluster.id; 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);
} }
}, [props.selectedCluster]) });
useEffect(()=>{ return timeFields;
if(queryParam){ };
useEffect(() => {
if (queryParam) {
queryStringManager.setQuery({ queryStringManager.setQuery({
query: queryParam, query: queryParam,
language: 'kuery' language: "kuery",
}); });
} }
const {http} = getContext(); const { http } = getContext();
const initialFetch = async ()=>{ 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({ const ils = await services.savedObjects.savedObjectsClient.find({
type: 'index-pattern', type: "view",
fields: ['title'], fields: ["title"],
search:'', search: "",
searchFields: ['title'], searchFields: ["title"],
perPage: 100, perPage: 100,
}); });
if(ils.length === 0){
props.history.push('/data/views/'); if (ils.length === 0 && indexNames.length === 0) {
return props.history.push("/data/views/");
return;
} }
const defaultIndex = viewID || await http.fetch(http.getServerBasePath()+'/setting/defaultIndex') let defaultIndex = viewID;
const targetIndex = ils.filter(il=>il.id == defaultIndex); let defaultIP = null;
const defaultIP = await services.indexPatternService.get(targetIndex.length > 0 ? defaultIndex : ils[0]?.id) 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({ setState({
indexPatternList: ils, indexPatternList: ils,
indexPattern: defaultIP indexPattern: defaultIP,
indices: indexNames,
timeFields,
}); });
if(!viewID){ // if (!viewID) {
setViewID(defaultIP.id); // setViewID(defaultIP.id);
} // }
} };
initialFetch(); initialFetch();
// return ()=>{ // return ()=>{
// queryStringManager.setQuery(''); // queryStringManager.setQuery('');
// } // }
},[props.selectedCluster]); }, [props.selectedCluster]);
function changeIndexPattern(indexPattern){ const changeIndexPattern = React.useCallback(
(indexPattern) => {
const timeFields = getTimeFields(indexPattern);
setState({ setState({
...state, ...state,
indexPattern, indexPattern,
timeFields,
}); });
if (indexPattern.type == "index") {
setIndex(indexPattern.id);
setViewID(undefined);
} else {
setViewID(indexPattern.id); setViewID(indexPattern.id);
setIndex(undefined);
} }
},
[state]
);
return ( return (state.indexPatternList && state.indexPatternList.length > 0) ||
state.indexPatternList && state.indexPatternList.length > 0 ? state.indices.length > 0 ? (
<Discover {...props} {...state} changeIndexPattern={changeIndexPattern} /> : <div style={{height:'100%', width:'100%', display: 'flex',flexDirection:'column', justifyContent:'center'}}><Spin tip='正在初始化 ......'/></div> <Discover {...props} {...state} changeIndexPattern={changeIndexPattern} />
) ) : (
} <div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
}}
>
<Spin tip="正在初始化 ......" />
</div>
);
};
const DiscoverContainer = (props)=>{ const DiscoverContainer = (props) => {
if(props.selectedCluster.id == ""){ if (props.selectedCluster.id == "") {
return null; return null;
} }
return <QueryParamProvider ReactRouterRoute={Route}> return (
<DiscoverUI {...props}/> <QueryParamProvider ReactRouterRoute={Route}>
<DiscoverUI {...props} />
</QueryParamProvider> </QueryParamProvider>
} );
};
export default connect(({ export default connect(({ global }) => ({
global
})=>({
selectedCluster: global.selectedCluster, selectedCluster: global.selectedCluster,
}))(DiscoverContainer) }))(DiscoverContainer);

View File

@ -135,12 +135,7 @@ class Index extends PureComponent {
title: '索引名称', title: '索引名称',
dataIndex: 'index', dataIndex: 'index',
render: (text, record) => ( render: (text, record) => (
<a onClick={()=>{ <Link to={`/data/discover?viewID=${text}`}>{text}</Link>
this.setState({
editingIndex: record,
drawerVisible: true,
});
}}>{text}</a>
) )
}, },
{ {
@ -162,8 +157,13 @@ class Index extends PureComponent {
title: '操作', title: '操作',
render: (text, record) => ( render: (text, record) => (
<Fragment> <Fragment>
{/* <a onClick={() => this.handleUpdateModalVisible(true, record)}></a> <a onClick={() => {
<Divider type="vertical" /> */} this.setState({
editingIndex: record,
drawerVisible: true,
});
}}>设置</a>
<Divider type="vertical" />
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDeleteClick(record.index)}> <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDeleteClick(record.index)}>
<a>删除</a> <a>删除</a>
</Popconfirm> </Popconfirm>

View File

@ -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 (
<div className="dscSetting setting-content" ref={settingRef}>
<div className="setting-wrapper">
<div className="setting-row">
<span className="label">Time Field</span>
<div>
<EuiSelect
id="timefield"
hasNoInitialSelection={true}
value={indexPattern?.timeFieldName}
options={timeFields.map((tf) => {
return {
value: tf,
text: tf,
};
})}
onChange={(e) => {
onTimeFieldChange(indexPattern.id, e.target.value);
}}
/>
</div>
</div>
{indexPattern.timeFieldName ? (
<div className="setting-row">
<span className="label">Time Interval</span>
<div>
<EuiSelect
aria-label={"Time interval"}
compressed
id="dscResultsIntervalSelector"
options={options
.filter(({ val }) => val !== "custom")
.map(({ display, val }) => {
return {
text: display,
value: val,
label: display,
};
})}
value={interval}
onChange={handleIntervalChange}
append={
bucketInterval.scaled ? (
<EuiIconTip
id="discoverIntervalIconTip"
content={`This interval creates ${
bucketInterval?.scale && bucketInterval?.scale > 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
)
}
/>
</div>
</div>
) : null}
</div>
</div>
);
};

View File

@ -21,7 +21,8 @@
.dscPageContent__wrapper { .dscPageContent__wrapper {
padding: 0 5 5 0; padding: 0 5 5 0;
overflow: hidden; // Ensures horizontal scroll of table 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: 1px solid #D3DAE6;
border-radius: 4px; border-radius: 4px;
flex-grow: 1; flex-grow: 1;
@ -31,7 +32,7 @@
//border: $euiBorderThin; //border: $euiBorderThin;
// text-align: center; // text-align: center;
box-shadow: none !important; box-shadow: none !important;
.euiDataGrid--fullScreen{ .euiDataGrid--fullScreen {
z-index: 801; z-index: 801;
} }
position: relative; position: relative;
@ -57,7 +58,7 @@
// SASSTODO: the visualizing component should have an option or a modifier // SASSTODO: the visualizing component should have an option or a modifier
.series > rect { .series > rect {
fill-opacity: .5; fill-opacity: 0.5;
stroke-width: 1; stroke-width: 1;
} }
} }
@ -75,23 +76,24 @@
// padding: 5 5 0 5; // padding: 5 5 0 5;
// } // }
.euiDataGrid--headerUnderline .euiDataGridHeaderCell { .euiDataGrid--headerUnderline .euiDataGridHeaderCell {
border-bottom: 1px solid #D3DAE6 !important; border-bottom: 1px solid #d3dae6 !important;
border-top: none !important; border-top: none !important;
} }
.dscSidebar{ .dscSidebar {
.euiButton--text, .euiButton--text:hover{ .euiButton--text,
.euiButton--text:hover {
background-color: #fff; background-color: #fff;
} }
} }
.sidebar-list{ .sidebar-list {
max-width: 200px; max-width: 200px;
} }
.euiSuperUpdateButton.euiButton--success { .euiSuperUpdateButton.euiButton--success {
background-color: #017D73 !important; background-color: #017d73 !important;
border-color: #017D73 !important; border-color: #017d73 !important;
color: #FFF !important; color: #fff !important;
border-radius: 0 !important; border-radius: 0 !important;
} }
@ -109,6 +111,64 @@
background-color: transparent; background-color: transparent;
} }
.kbnQueryBar__wrap{ .kbnQueryBar__wrap {
box-shadow: none; box-shadow: none;
} }
.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);
}
}

View File

@ -1,25 +1,40 @@
import Console from '../../components/kibana/console/components/Console'; import Console from "../../components/kibana/console/components/Console";
import {connect} from 'dva'; import { connect } from "dva";
import {Button, Icon, Menu, Dropdown, } from 'antd'; import { Button, Icon, Menu, Dropdown } from "antd";
import Tabs from '@/components/infini/tabs'; import Tabs from "@/components/infini/tabs";
import {DraggableTabs} from '@/components/infini/tabs/DraggableTabs'; import { DraggableTabs } from "@/components/infini/tabs/DraggableTabs";
import {useState, useReducer, useCallback, useEffect, useMemo, useRef, useLayoutEffect} from 'react'; import {
import {useLocalStorage} from '@/lib/hooks/storage'; useState,
import {setClusterID} from '../../components/kibana/console/modules/mappings/mappings'; useReducer,
import {TabTitle} from './console_tab_title'; useCallback,
import '@/assets/utility.scss'; 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 { Resizable } from "re-resizable";
import {ResizeBar} from '@/components/infini/resize_bar'; import { ResizeBar } from "@/components/infini/resize_bar";
import NewTabMenu from './NewTabMenu'; import NewTabMenu from "./NewTabMenu";
import maximizeSvg from "@/assets/window-maximize.svg";
import restoreSvg from "@/assets/window-restore.svg";
const MaximizeIcon = (props = {}) => {
return <img height="14px" width="14px" {...props} src={maximizeSvg} />;
};
const RestoreIcon = (props = {}) => {
return <img height="14px" width="14px" {...props} src={restoreSvg} />;
};
const { TabPane } = Tabs; const { TabPane } = Tabs;
const TabConsole = (props:any)=>{ const TabConsole = (props: any) => {
return ( return <Console {...props} />;
<Console {...props}/> };
)
}
// export default connect(({ // export default connect(({
// global // global
@ -29,288 +44,320 @@ const TabConsole = (props:any)=>{
const addTab = (state: any, action: any) => { const addTab = (state: any, action: any) => {
const { panes } = state; const { panes } = state;
const {cluster} = action.payload; const { cluster } = action.payload;
const activeKey = `${cluster.id}:${new Date().valueOf()}`; 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 { return {
...state, ...state,
panes, panes,
activeKey, activeKey,
} };
} };
const removeTab = (state: any, action: any) =>{ const removeTab = (state: any, action: any) => {
const { activeKey, panes } = state; const { activeKey, panes } = state;
const {targetKey} = action.payload; const { targetKey } = action.payload;
const newPanes = panes.filter(pane => pane.key !== targetKey);
const newPanes = panes.filter((pane) => pane.key !== targetKey);
return { return {
...state, ...state,
panes: newPanes, panes: newPanes,
activeKey: panes[0]?.key, activeKey: activeKey == targetKey ? newPanes[0]?.key : activeKey,
} };
} };
const consoleTabReducer = (state: any, action: any) => { const consoleTabReducer = (state: any, action: any) => {
const {type, payload} = action; const { type, payload } = action;
let newState = state; let newState = state;
switch(type){ switch (type) {
case 'add': case "add":
newState = addTab(state, action); newState = addTab(state, action);
break; break;
case 'remove': case "remove":
newState = removeTab(state, action); newState = removeTab(state, action);
break; break;
case 'change': case "change":
newState = { newState = {
...state, ...state,
activeKey: payload.activeKey, activeKey: payload.activeKey,
} };
break; break;
case 'saveTitle': case "saveTitle":
const {key, title} = action.payload; const { key, title } = action.payload;
const newPanes = state.panes.map((pane: any)=>{ const newPanes = state.panes.map((pane: any) => {
if(pane.key == key){ if (pane.key == key) {
return { return {
...pane, ...pane,
title, title,
} };
} }
return pane; return pane;
}); });
newState = { newState = {
...state, ...state,
panes: newPanes, panes: newPanes,
} };
break; break;
case 'saveContent': case "saveContent":
const panes = state.panes.map((pane)=>{ const panes = state.panes.map((pane) => {
if(pane.key == state.activeKey){ if (pane.key == state.activeKey) {
return { return {
...pane, ...pane,
content: action.payload.content, content: action.payload.content,
} };
} }
return pane; return pane;
}); });
newState = ({ newState = {
...state, ...state,
panes, panes,
}); };
break; break;
case 'saveOrder': case "saveOrder":
newState = ({ newState = {
...state, ...state,
order: action.payload.order, order: action.payload.order,
}); };
default: default:
} }
// setLocalState(newState); // setLocalState(newState);
return newState; return newState;
} };
function calcHeightToPX(height: string){ function calcHeightToPX(height: string) {
const intHeight = parseInt(height) const intHeight = parseInt(height);
if(height.endsWith('vh')){ if (height.endsWith("vh")) {
return Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) * intHeight / 100; return (
}else{ (Math.max(
document.documentElement.clientHeight || 0,
window.innerHeight || 0
) *
intHeight) /
100
);
} else {
return intHeight; return intHeight;
} }
} }
export const ConsoleUI = ({selectedCluster, export const ConsoleUI = ({
selectedCluster,
clusterList, clusterList,
clusterStatus, clusterStatus,
minimize=false, minimize = false,
onMinimizeClick, onMinimizeClick,
resizeable=false, resizeable = false,
height='50vh' height = "50vh",
}: any)=>{ }: any) => {
const clusterMap = useMemo(()=>{ const clusterMap = useMemo(() => {
let cm = {}; let cm = {};
if(!clusterStatus){ if (!clusterStatus) {
return cm; return cm;
} }
(clusterList || []).map((cluster: any)=>{ (clusterList || []).map((cluster: any) => {
cluster.status = clusterStatus[cluster.id]?.health?.status; cluster.status = clusterStatus[cluster.id]?.health?.status;
if(!clusterStatus[cluster.id]?.available){ if (!clusterStatus[cluster.id]?.available) {
cluster.status = 'unavailable'; cluster.status = "unavailable";
} }
cm[cluster.id] = cluster; cm[cluster.id] = cluster;
}); });
return cm; return cm;
}, [clusterList, clusterStatus]) }, [clusterList, clusterStatus]);
const initialDefaultState = ()=>{ const initialDefaultState = () => {
let defaultCluster = selectedCluster; let defaultCluster = selectedCluster;
if(!defaultCluster.id){ if (!defaultCluster.id) {
defaultCluster = clusterList[0] ; defaultCluster = clusterList[0];
} }
const defaultActiveKey = `${defaultCluster.id || ''}:${new Date().valueOf()}`; const defaultActiveKey = `${defaultCluster.id ||
const defaultState = defaultCluster? { ""}:${new Date().valueOf()}`;
panes:[{ const defaultState = defaultCluster
key: defaultActiveKey, cluster_id: defaultCluster.id, title: defaultCluster.name ? {
}], panes: [
{
key: defaultActiveKey,
cluster_id: defaultCluster.id,
title: defaultCluster.name,
},
],
activeKey: defaultActiveKey, activeKey: defaultActiveKey,
}: {panes:[],activeKey:''};
return defaultState
} }
: { panes: [], activeKey: "" };
const [localState, setLocalState, removeLocalState] = useLocalStorage("console:state", initialDefaultState, { return defaultState;
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<HTMLElement, 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<HTMLElement, MouseEvent>,
action: string
) => {
dispatch({ dispatch({
type: action, type: action,
payload: { payload: {
targetKey, targetKey,
} },
}) });
}; };
const newTabClick = useCallback((param: any)=>{ const newTabClick = useCallback(
const cluster = clusterList.find(item=>item.id == param.id); (param: any) => {
if(!cluster){ const cluster = clusterList.find((item) => item.id == param.id);
console.log('cluster not found') if (!cluster) {
console.log("cluster not found");
return; return;
} }
dispatch({ dispatch({
type:'add', type: "add",
payload: { payload: {
cluster, cluster,
} },
}) });
},[clusterList]) },
[clusterList]
const menu = (
// <Menu onClick={newTabClick}>
// {(clusterList||[]).map((cluster:any)=>{
// return <Menu.Item key={cluster.id}>{cluster.name}</Menu.Item>
// })}
// </Menu>
<NewTabMenu data={clusterList}
onItemClick={newTabClick}
clusterStatus={clusterStatus}
size={10}
width="300px"/>
); );
// const menu = (
// // <Menu onClick={newTabClick}>
// // {(clusterList||[]).map((cluster:any)=>{
// // return <Menu.Item key={cluster.id}>{cluster.name}</Menu.Item>
// // })}
// // </Menu>
// <NewTabMenu data={clusterList}
// onItemClick={newTabClick}
// clusterStatus={clusterStatus}
// size={10}
// width="300px"/>
// );
const rootRef = useRef(null); const rootRef = useRef(null);
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const fullscreenClick = ()=>{ const fullscreenClick = () => {
if(rootRef.current != null){ if (rootRef.current != null) {
if(!isFullscreen){ if (!isFullscreen) {
rootRef.current.className = rootRef.current.className + " fullscreen"; rootRef.current.className = rootRef.current.className + " fullscreen";
// rootRef.current.style.overflow = 'scroll'; // rootRef.current.style.overflow = 'scroll';
}else{ } else {
rootRef.current.className = rootRef.current.className.replace(' fullscreen', ''); rootRef.current.className = rootRef.current.className.replace(
" fullscreen",
""
);
} }
} }
setEditorHeight(rootRef.current.clientHeight) setEditorHeight(rootRef.current.clientHeight);
setIsFullscreen(!isFullscreen) setIsFullscreen(!isFullscreen);
} };
const tabBarExtra ={ const tabBarExtra = {
left: <div> left: (
{minimize? <Button size="small" onClick={onMinimizeClick} style={{marginLeft:5}} title="Close"> <div className="tabbar-icon" onClick={onMinimizeClick}>
{/* <Icon type="minus"/> */} {minimize ? <Icon type="code" /> : null}
<Icon type="poweroff"/>
</Button>:null}
</div>,
right:(
<div>
{isFullscreen?
<Button size="small" onClick={fullscreenClick}>
<Icon type="fullscreen-exit"/>
</Button>:
<Button size="small" onClick={fullscreenClick} title="Fullscreen">
<Icon type="fullscreen"/>
</Button>
}
</div> </div>
), ),
append:( right: (
<div> <>
<Dropdown overlay={menu} placement="bottomLeft"> <div className="tabbar-icon" onClick={onMinimizeClick}>
<Button size="small" style={{marginRight:5}}> {minimize ? <Icon type="minus" /> : null}
<Icon type="plus"/>
</Button>
</Dropdown>
</div> </div>
) <div className="tabbar-icon" onClick={fullscreenClick}>
}; {isFullscreen ? <RestoreIcon /> : <MaximizeIcon />}
</div>
</>
),
append: (
<NewTabMenu
data={clusterList}
onItemClick={newTabClick}
clusterStatus={clusterStatus}
size={10}
width="300px"
>
<div className="tabbar-icon">
<Icon type="plus" />
</div>
</NewTabMenu>
),
};
setClusterID(tabState.activeKey?.split(':')[0]); setClusterID(tabState.activeKey?.split(":")[0]);
const panes = tabState.panes.filter((pane: any)=>{ const panes = tabState.panes.filter((pane: any) => {
return typeof clusterMap[pane.cluster_id] != 'undefined'; return typeof clusterMap[pane.cluster_id] != "undefined";
}) });
const saveTitle = (key: string, title: string)=>{ const saveTitle = (key: string, title: string) => {
dispatch({ dispatch({
type:'saveTitle', type: "saveTitle",
payload: { payload: {
key, key,
title, title,
} },
}) });
} };
const [editorHeight, setEditorHeight] = useState(calcHeightToPX(height)) const [editorHeight, setEditorHeight] = useState(calcHeightToPX(height));
const onResize = (_env, _dir, refToElement, delta)=>{ const onResize = (_env, _dir, refToElement, delta) => {
// console.log(refToElement.offsetHeight, delta) // console.log(refToElement.offsetHeight, delta)
setEditorHeight(refToElement.clientHeight) setEditorHeight(refToElement.clientHeight);
} };
const disableWindowScroll = ()=>{ const disableWindowScroll = () => {
document.body.style.overflow = 'hidden' document.body.style.overflow = "hidden";
} };
const enableWindowScroll = ()=>{ const enableWindowScroll = () => {
document.body.style.overflow = ''; document.body.style.overflow = "";
} };
const onTabNodeMoved=(newOrder:string[])=>{ const onTabNodeMoved = (newOrder: string[]) => {
dispatch({ dispatch({
type:'saveOrder', type: "saveOrder",
payload: { payload: {
order: newOrder, order: newOrder,
} },
}) });
} };
return ( return (
<Resizable <Resizable
defaultSize={{ defaultSize={{
height: editorHeight||'50vh' height: editorHeight || "50vh",
}} }}
minHeight={70} minHeight={70}
maxHeight="100vh" maxHeight="100vh"
handleComponent={{ top: <ResizeBar/> }} handleComponent={{ top: <ResizeBar /> }}
onResize={onResize} onResize={onResize}
enable={{ enable={{
top: resizeable, top: resizeable,
@ -321,12 +368,15 @@ export const ConsoleUI = ({selectedCluster,
bottomRight: false, bottomRight: false,
bottomLeft: false, bottomLeft: false,
topLeft: false, topLeft: false,
}}> }}
<div style={{background:'#fff', height:'100%'}} >
<div
style={{ background: "#fff", height: "100%" }}
onMouseOver={disableWindowScroll} onMouseOver={disableWindowScroll}
onMouseOut={enableWindowScroll} onMouseOut={enableWindowScroll}
id="console" id="console"
ref={rootRef} > ref={rootRef}
>
<DraggableTabs <DraggableTabs
onChange={onChange} onChange={onChange}
activeKey={tabState.activeKey} activeKey={tabState.activeKey}
@ -337,9 +387,26 @@ export const ConsoleUI = ({selectedCluster,
tabBarExtraContent={tabBarExtra} tabBarExtraContent={tabBarExtra}
onTabNodeMoved={onTabNodeMoved} onTabNodeMoved={onTabNodeMoved}
> >
{panes.map(pane => ( {panes.map((pane) => (
<TabPane tab={<TabTitle title={pane.title} onTitleChange={(title)=>{saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}> <TabPane
<TabConsole height={editorHeight - 40} selectedCluster={clusterMap[pane.cluster_id]} paneKey={pane.key} saveEditorContent={saveEditorContent} initialText={pane.content} /> tab={
<TabTitle
title={pane.title}
onTitleChange={(title) => {
saveTitle(pane.key, title);
}}
/>
}
key={pane.key}
closable={pane.closable}
>
<TabConsole
height={editorHeight - 35}
selectedCluster={clusterMap[pane.cluster_id]}
paneKey={pane.key}
saveEditorContent={saveEditorContent}
initialText={pane.content}
/>
{/* {pane.content} */} {/* {pane.content} */}
</TabPane> </TabPane>
))} ))}
@ -347,13 +414,11 @@ export const ConsoleUI = ({selectedCluster,
</div> </div>
</Resizable> </Resizable>
); );
} };
export default connect(({ export default connect(({ global }) => ({
global
})=>({
selectedCluster: global.selectedCluster, selectedCluster: global.selectedCluster,
clusterList: global.clusterList, clusterList: global.clusterList,
clusterStatus: global.clusterStatus, clusterStatus: global.clusterStatus,
height: window.innerHeight - 75 + 'px', height: window.innerHeight - 75 + "px",
}))(ConsoleUI); }))(ConsoleUI);

View File

@ -22,11 +22,16 @@ class NewTabMenu extends React.Component{
initialLoad: true, initialLoad: true,
dataSource: [...props.data], dataSource: [...props.data],
dataSourceKey: 1, dataSourceKey: 1,
selectedIndex: -1,
overlayVisible: false,
} }
} }
componentDidMount(){ componentDidMount(){
} }
handleInfiniteOnLoad = (current) => { handleInfiniteOnLoad = (current) => {
let {size } = this.props; let {size } = this.props;
let targetLength = current * size; let targetLength = current * size;
@ -54,17 +59,56 @@ class NewTabMenu extends React.Component{
dataSource: newData, dataSource: newData,
data: newData, data: newData,
hasMore: newData.length > this.props.size, 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(){ render(){
const {clusterStatus} = this.props; const {clusterStatus} = this.props;
return (<div className={styles.dropmenu} style={{width: this.props.width}}> const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}>
<div className={styles.infiniteContainer} style={{height: this.props.height}}> <div className={styles.infiniteContainer} style={{height: this.props.height}}
onMouseEnter={()=>{this.searchInputRef.focus()}}
tabIndex="0" onKeyDown={this.onKeyDown}>
<div className={styles.filter} style={{paddingTop: 10, paddingBottom:0}}> <div className={styles.filter} style={{paddingTop: 10, paddingBottom:0}}>
<input className={styles['btn-ds']} style={{outline:'none'}} onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} /> <input className={styles['btn-ds']} style={{outline:'none'}}
ref={(ref)=>{this.searchInputRef= ref;}}
onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} />
</div> </div>
<InfiniteScroll <InfiniteScroll
initialLoad={this.state.initialLoad} initialLoad={this.state.initialLoad}
@ -74,11 +118,12 @@ class NewTabMenu extends React.Component{
> >
<div className={styles.dslist}> <div className={styles.dslist}>
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>} {(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
{(this.state.data || []).map((item)=>{ {(this.state.data || []).map((item, idx)=>{
const cstatus = clusterStatus ? clusterStatus[item.id] : null; const cstatus = clusterStatus ? clusterStatus[item.id] : null;
return <DropdownItem key={item.id} return <DropdownItem key={item.id}
clusterItem={item} clusterItem={item}
clusterStatus={cstatus} clusterStatus={cstatus}
isSelected={this.state.selectedIndex == idx}
onClick={() => { onClick={() => {
this.handleItemClick(item) this.handleItemClick(item)
}} }}
@ -93,6 +138,17 @@ class NewTabMenu extends React.Component{
</div> </div>
)} )}
</div>); </div>);
return (
<div>
<Dropdown overlay={menu} placement="bottomLeft"
visible={this.state.overlayVisible}
onVisibleChange={(flag)=>{
this.setState({ overlayVisible: flag });
}}>
{this.props.children}
</Dropdown>
</div>
)
} }
} }

View File

@ -1,15 +1,28 @@
.tab-title{ .tab-title{
display: inline-block; display: inline-block;
height: 30px;
overflow: hidden;
.input-eidtor{ .input-eidtor{
border-radius: 0; border-radius: 0;
border:none; border:none;
border-bottom: 1px solid #ccc; // border-bottom: 1px solid #ccc;
border-right: none; border-right: none;
padding-left: 1em; padding-left: 1em;
color:rgba(0, 0, 0, 0.65);
height: 28px;
line-height: 28px;
background-color: #939ea0;
color: #fff;
&:focus{ &:focus{
outline: none; outline: none;
} }
} }
.icon-cont{
overflow: hidden;
height: 100%;
display: flex;
align-items: center;
}
} }
#console{ #console{

View File

@ -26,16 +26,26 @@ export const TabTitle = ({title, onTitleChange}: TabTitleProps)=>{
} }
},[editable]) },[editable])
const inputRef = useRef(null); const inputRef = useRef(null);
const onKeyDown = (e: any)=>{
const { which } = e;
switch (which) {
case 13:
e.target.blur();
break;
}
}
return (<div title="double click to change title" className="tab-title" onDoubleClick={()=>{ return (<div title="double click to change title" className="tab-title" onDoubleClick={()=>{
setEditable(true) setEditable(true)
}}> }}>
{editable ? <input ref={inputRef} className="input-eidtor" {editable ? <input ref={inputRef} className="input-eidtor" onKeyDown={onKeyDown}
type="text" value={value} type="text" value={value}
onBlur={()=>{ onBlur={()=>{
setEditable(false) setEditable(false)
}} }}
onChange={onValueChange}/>: onChange={onValueChange}/>:
<div style={{display:'flex', alignItems:'center'}}><Icon component={ElasticIcon} />{value}</div>} <div className="icon-cont"><Icon component={ElasticIcon} />{value}</div>}
</div>) </div>)
} }