modify discover and console
This commit is contained in:
parent
8685936eb1
commit
354bcd87bb
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
2
main.go
2
main.go
|
@ -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")
|
||||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
max-width: calc(100% - 140px);
|
.ant-tabs-top-bar .ant-tabs-nav-container {
|
||||||
|
max-width: calc(100% - 140px);
|
||||||
|
}
|
||||||
|
.tabbar-icon {
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tabbar-icon:hover {
|
||||||
|
background-color: #efefef;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ant-tabs-extra-append{
|
.ant-tabs-extra-append {
|
||||||
order: 3;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,74 +78,87 @@ 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>
|
||||||
<EuiBadge color="default">{selectedCluster.host}</EuiBadge>
|
<FormattedMessage id="console.cluster.endpoint" />:
|
||||||
|
</span>
|
||||||
|
<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>
|
||||||
<EuiToolTip
|
<FormattedMessage id="console.response.status" />:
|
||||||
position="top"
|
</span>
|
||||||
content={
|
<EuiToolTip
|
||||||
<EuiText size="s">{`${method} ${
|
position="top"
|
||||||
endpoint.startsWith('/') ? endpoint : '/' + endpoint
|
content={
|
||||||
}`}</EuiText>
|
<EuiText size="s">{`${method} ${
|
||||||
}
|
endpoint.startsWith("/") ? endpoint : "/" + endpoint
|
||||||
>
|
}`}</EuiText>
|
||||||
<EuiText size="s">
|
}
|
||||||
<EuiBadge color={mapStatusCodeToBadgeColor(statusCode)}>
|
>
|
||||||
{/* Use to ensure that no matter the width we don't allow line breaks */}
|
|
||||||
{statusCode} - {statusText}
|
|
||||||
</EuiBadge>
|
|
||||||
</EuiText>
|
|
||||||
</EuiToolTip>
|
|
||||||
</div>
|
|
||||||
<div className="info-item">
|
|
||||||
<span><FormattedMessage id="console.response.time_elapsed"/>:</span>
|
|
||||||
<EuiToolTip
|
|
||||||
position="top"
|
|
||||||
content={
|
|
||||||
<EuiText size="s">
|
<EuiText size="s">
|
||||||
Time Elapsed
|
<EuiBadge color={mapStatusCodeToBadgeColor(statusCode)}>
|
||||||
|
{/* Use to ensure that no matter the width we don't allow line breaks */}
|
||||||
|
{statusCode} - {statusText}
|
||||||
|
</EuiBadge>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
}
|
</EuiToolTip>
|
||||||
>
|
</div>
|
||||||
<EuiText size="s">
|
<div className="info-item">
|
||||||
<EuiBadge color="default">
|
<span>
|
||||||
{timeElapsedMs} {'ms'}
|
<FormattedMessage id="console.response.time_elapsed" />:
|
||||||
</EuiBadge>
|
</span>
|
||||||
</EuiText>
|
<EuiToolTip
|
||||||
</EuiToolTip>
|
position="top"
|
||||||
</div>
|
content={<EuiText size="s">Time Elapsed</EuiText>}
|
||||||
|
>
|
||||||
|
<EuiText size="s">
|
||||||
|
<EuiBadge color="default">
|
||||||
|
{timeElapsedMs} {"ms"}
|
||||||
|
</EuiBadge>
|
||||||
|
</EuiText>
|
||||||
|
</EuiToolTip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(
|
||||||
pattern: title,
|
spec.fields || {},
|
||||||
metaFields: await this.config.get(UI_SETTINGS.META_FIELDS),
|
id,
|
||||||
type,
|
spec.title as string,
|
||||||
params: typeMeta && typeMeta.params,
|
{
|
||||||
})
|
pattern: title,
|
||||||
|
metaFields: await this.config.get(UI_SETTINGS.META_FIELDS),
|
||||||
|
type,
|
||||||
|
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(
|
||||||
id: indexPattern.id,
|
savedObjectType,
|
||||||
});
|
body,
|
||||||
|
{
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -603,10 +665,10 @@ export class IndexPatternsService {
|
||||||
if (ignoreErrors) {
|
if (ignoreErrors) {
|
||||||
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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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,61 +22,83 @@ 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>
|
||||||
<table className="kbn-table table">
|
<TableContext.Provider value={{ tableRef: tableRef.current }}>
|
||||||
<thead>
|
<table className="kbn-table table">
|
||||||
<TableHeader columns={columns}
|
<thead>
|
||||||
defaultSortOrder={''}
|
<TableHeader
|
||||||
hideTimeColumn={false}
|
columns={columns}
|
||||||
indexPattern={indexPattern}
|
defaultSortOrder={"desc"}
|
||||||
isShortDots={false}
|
hideTimeColumn={false}
|
||||||
onChangeSortOrder={onChangeSortOrder}
|
indexPattern={indexPattern}
|
||||||
onMoveColumn={onMoveColumn}
|
isShortDots={false}
|
||||||
onRemoveColumn={onRemoveColumn}
|
onChangeSortOrder={onChangeSortOrder}
|
||||||
sortOrder={sortOrder||[]}/>
|
onMoveColumn={onMoveColumn}
|
||||||
</thead>
|
onRemoveColumn={onRemoveColumn}
|
||||||
<tbody>
|
sortOrder={sortOrder || []}
|
||||||
{hits.slice(0, scrollState.limit).map((row, idx)=>{
|
|
||||||
|
|
||||||
return <TableRow key={'discover-table-row'+row._id} onFilter={onFilter}
|
|
||||||
columns={columns}
|
|
||||||
hideTimeColumn={false}
|
|
||||||
indexPattern={indexPattern}
|
|
||||||
isShortDots={false}
|
|
||||||
onAddColumn={onAddColumn}
|
|
||||||
onRemoveColumn={onRemoveColumn}
|
|
||||||
row={row}
|
|
||||||
document={document}
|
|
||||||
/>
|
/>
|
||||||
})}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{hits.slice(0, scrollState.limit).map((row, idx) => {
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={"discover-table-row" + row._id}
|
||||||
|
onFilter={onFilter}
|
||||||
|
columns={columns}
|
||||||
|
hideTimeColumn={false}
|
||||||
|
indexPattern={indexPattern}
|
||||||
|
isShortDots={false}
|
||||||
|
onAddColumn={onAddColumn}
|
||||||
|
onRemoveColumn={onRemoveColumn}
|
||||||
|
row={row}
|
||||||
|
document={document}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</TableContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
@ -89,8 +112,8 @@ const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, o
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Table;
|
export default React.memo(Table);
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
const TableContext = React.createContext({
|
||||||
|
tableRef: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TableContext;
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,28 +42,28 @@ 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);
|
||||||
|
|
||||||
function handleEditorDidMount(editor, monaco) {
|
function handleEditorDidMount(editor, monaco) {
|
||||||
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,
|
||||||
|
@ -57,18 +71,18 @@ export function Detail({
|
||||||
_type: row._type,
|
_type: row._type,
|
||||||
_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,72 +90,117 @@ 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
|
||||||
deleteDocumentClick();
|
title="sure to delete"
|
||||||
}}><a> Delete </a></Popconfirm>
|
onConfirm={() => {
|
||||||
|
deleteDocumentClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a> Delete </a>
|
||||||
|
</Popconfirm>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<td colSpan={ columns.length + 2 }>
|
<td
|
||||||
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween">
|
colSpan={columns.length + 2}
|
||||||
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
style={{
|
||||||
<div className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow">
|
wordBreak: "break-all",
|
||||||
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
wordWrap: "break-word",
|
||||||
<EuiIcon type="folderOpen" size="m" style={{marginTop: 5}}/>
|
whiteSpace: "pre-wrap",
|
||||||
</div>
|
}}
|
||||||
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
>
|
||||||
<h4
|
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween">
|
||||||
data-test-subj="docTableRowDetailsTitle"
|
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||||
className="euiTitle euiTitle--xsmall"
|
<div className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow">
|
||||||
>Expanded document</h4>
|
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
||||||
</div>
|
<EuiIcon type="folderOpen" size="m" style={{ marginTop: 5 }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
||||||
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
<h4
|
||||||
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow">
|
data-test-subj="docTableRowDetailsTitle"
|
||||||
<Drawer title="Edit document" visible={editorVisible} width="640" destroyOnClose={true}
|
className="euiTitle euiTitle--xsmall"
|
||||||
onClose={()=>{setEditorVisble(false)}}>
|
>
|
||||||
<Descriptions>
|
Expanded document
|
||||||
<Descriptions.Item label="_index">{row._index}</Descriptions.Item>
|
</h4>
|
||||||
<Descriptions.Item label="_id">{row._id}</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
<Editor
|
|
||||||
height="70vh"
|
|
||||||
theme="vs-light"
|
|
||||||
language="json"
|
|
||||||
options={{
|
|
||||||
minimap: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
tabSize: 2,
|
|
||||||
wordBasedSuggestions: true,
|
|
||||||
}}
|
|
||||||
value={JSON.stringify(row._source, null, 2)}
|
|
||||||
onMount={handleEditorDidMount}
|
|
||||||
/>
|
|
||||||
<div style={{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> */}
|
|
||||||
<SaveAsNewButton docID={row._id} saveDocumentClick={saveDocumentClick}/>
|
|
||||||
<Button type="primary" onClick={()=>{saveDocumentClick()}} >Save</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</div>
|
||||||
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||||
{/* <a
|
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow">
|
||||||
|
<Drawer
|
||||||
|
title="Edit document"
|
||||||
|
visible={editorVisible}
|
||||||
|
width="640"
|
||||||
|
destroyOnClose={true}
|
||||||
|
onClose={() => {
|
||||||
|
setEditorVisble(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Descriptions>
|
||||||
|
<Descriptions.Item label="_index">
|
||||||
|
{row._index}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="_id">{row._id}</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
<Editor
|
||||||
|
height="70vh"
|
||||||
|
theme="vs-light"
|
||||||
|
language="json"
|
||||||
|
options={{
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
tabSize: 2,
|
||||||
|
wordBasedSuggestions: true,
|
||||||
|
}}
|
||||||
|
value={JSON.stringify(row._source, null, 2)}
|
||||||
|
onMount={handleEditorDidMount}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
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> */}
|
||||||
|
<SaveAsNewButton
|
||||||
|
docID={row._id}
|
||||||
|
saveDocumentClick={saveDocumentClick}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
saveDocumentClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||||
|
{/* <a
|
||||||
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
|
||||||
Operation <Icon type="down" />
|
className="ant-dropdown-link"
|
||||||
</a>
|
onClick={(e) => e.preventDefault()}
|
||||||
</Dropdown>
|
>
|
||||||
</div>
|
Operation <Icon type="down" />
|
||||||
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
</a>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||||
<a
|
<a
|
||||||
className="euiLink"
|
className="euiLink"
|
||||||
>View surrounding documents</a>
|
>View surrounding documents</a>
|
||||||
|
@ -151,45 +210,58 @@ export function Detail({
|
||||||
className="euiLink"
|
className="euiLink"
|
||||||
>View single document</a>
|
>View single document</a>
|
||||||
</div> */}
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div data-test-subj="docViewer">
|
||||||
</div>
|
<DocViewer
|
||||||
<div data-test-subj="docViewer">
|
columns={columns}
|
||||||
<DocViewer
|
filter={onFilter}
|
||||||
columns={columns}
|
hit={row}
|
||||||
filter={onFilter}
|
indexPattern={indexPattern}
|
||||||
hit={row}
|
onAddColumn={onAddColumn}
|
||||||
indexPattern={indexPattern}
|
onRemoveColumn={onRemoveColumn}
|
||||||
onAddColumn={onAddColumn}
|
/>
|
||||||
onRemoveColumn={onRemoveColumn}
|
</div>
|
||||||
/>
|
</td>
|
||||||
</div>
|
);
|
||||||
|
|
||||||
</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}
|
||||||
title="Please input new ID"
|
title="Please input new ID"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
// 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">
|
||||||
</Popover>
|
Save as new
|
||||||
)
|
</Button>
|
||||||
}
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -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,62 +27,89 @@ 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}
|
/>
|
||||||
row={row}
|
{indexPattern.timeFieldName && !hideTimeColumn ? (
|
||||||
indexPattern={indexPattern}
|
<Cell
|
||||||
inlineFilter={onFilter}
|
timefield={true}
|
||||||
formatted={_displayField(indexPattern, row, indexPattern.timeFieldName)}
|
row={row}
|
||||||
filterable={mapping(indexPattern.timeFieldName).filterable} //&& $scope.filter
|
indexPattern={indexPattern}
|
||||||
column= {indexPattern.timeFieldName}/>: null}
|
inlineFilter={onFilter}
|
||||||
|
formatted={_displayField(
|
||||||
|
indexPattern,
|
||||||
|
row,
|
||||||
|
indexPattern.timeFieldName
|
||||||
|
)}
|
||||||
|
filterable={mapping(indexPattern.timeFieldName).filterable} //&& $scope.filter
|
||||||
|
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 (
|
||||||
row={row}
|
<Cell
|
||||||
inlineFilter={onFilter}
|
key={"discover-cell-" + column}
|
||||||
indexPattern={indexPattern}
|
timefield={false}
|
||||||
sourcefield={column === '_source'}
|
row={row}
|
||||||
formatted={_displayField(indexPattern, row, column, true)}
|
inlineFilter={onFilter}
|
||||||
filterable={isFilterable} //&& $scope.filter
|
indexPattern={indexPattern}
|
||||||
column= {column}/>
|
sourcefield={column === "_source"}
|
||||||
|
formatted={_displayField(indexPattern, row, column, true)}
|
||||||
|
filterable={isFilterable} //&& $scope.filter
|
||||||
|
column={column}
|
||||||
|
/>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{open? <tr className="kbnDocTableDetails__row">
|
{open && (
|
||||||
<Detail columns={columns}
|
<tr className="kbnDocTableDetails__row">
|
||||||
indexPattern={indexPattern}
|
<MemoDetail
|
||||||
row={row}
|
columns={columns}
|
||||||
document={document}
|
indexPattern={indexPattern}
|
||||||
onFilter={onFilter}
|
row={row}
|
||||||
onAddColumn={onAddColumn}
|
document={document}
|
||||||
onRemoveColumn={onRemoveColumn}/>
|
onFilter={onFilter}
|
||||||
</tr>:null
|
onAddColumn={onAddColumn}
|
||||||
}
|
onRemoveColumn={onRemoveColumn}
|
||||||
|
/>
|
||||||
|
</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 }} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,28 +15,27 @@ 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,
|
||||||
};
|
};
|
||||||
|
@ -62,7 +61,7 @@ export class DiscoverHistogram extends Component{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onBrushEnd = ({ x }) => {
|
onBrushEnd = ({ x }) => {
|
||||||
if (!x) {
|
if (!x) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -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,28 +129,33 @@ 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}
|
||||||
onElementClick={this.onElementClick(xInterval)}
|
onElementClick={this.onElementClick(xInterval)}
|
||||||
tooltip={tooltipProps}
|
tooltip={tooltipProps}
|
||||||
theme={LIGHT_THEME}
|
theme={LIGHT_THEME}
|
||||||
// baseTheme={chartsBaseTheme}
|
// baseTheme={chartsBaseTheme}
|
||||||
/>
|
/>
|
||||||
<Axis
|
<Axis
|
||||||
id="discover-histogram-left-axis"
|
id="discover-histogram-left-axis"
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
fields,
|
} = useMemo(() => {
|
||||||
columns,
|
const groupedFields = groupFields(
|
||||||
popularLimit,
|
fields,
|
||||||
fieldCounts,
|
columns,
|
||||||
fieldFilterState,
|
popularLimit,
|
||||||
]);
|
fieldCounts,
|
||||||
|
fieldFilterState
|
||||||
|
);
|
||||||
|
const fieldsTree = {};
|
||||||
|
groupedFields.unpopular.forEach((field) => {
|
||||||
|
const keys = field.displayName.split(".");
|
||||||
|
let currentObj = fieldsTree;
|
||||||
|
keys.forEach((key: string, i: number) => {
|
||||||
|
if (!currentObj[key]) {
|
||||||
|
currentObj[key] = {};
|
||||||
|
}
|
||||||
|
if (keys.length == i + 1) {
|
||||||
|
field.isLeaf = true;
|
||||||
|
currentObj[key] = field;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentObj = currentObj[key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...groupedFields,
|
||||||
|
fieldsTree,
|
||||||
|
};
|
||||||
|
}, [fields, columns, popularLimit, fieldCounts, fieldFilterState]);
|
||||||
|
|
||||||
const fieldTypes = useMemo(() => {
|
const 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 (
|
||||||
|
<Tree.TreeNode
|
||||||
|
icon={<Icon type="carry-out" />}
|
||||||
|
selectable={false}
|
||||||
|
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 (
|
return (
|
||||||
<EuiHideFor sizes={['xs', 's']}>
|
<EuiHideFor sizes={["xs", "s"]}>
|
||||||
<section
|
<section className="sidebar-list" aria-label={"Index and fields"}>
|
||||||
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,9 +363,18 @@ 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
<EuiFlexItem grow={false}>
|
||||||
{`${toMoment(timeRange.from)} - ${toMoment(timeRange.to)} ${
|
<div>
|
||||||
interval !== 'auto'
|
Found <span style={{ fontSize: 16, fontWeight: "bold" }}>{hits}</span>{" "}
|
||||||
? 'per'
|
records
|
||||||
: ''
|
{timeRange && (
|
||||||
|
<span style={{ fontSize: 12, marginLeft: 5 }}>
|
||||||
|
{`between ${toMoment(timeRange.from)} and ${toMoment(
|
||||||
|
timeRange.to
|
||||||
|
)} ${
|
||||||
|
interval !== "auto" ? "" : "" //per
|
||||||
}`}
|
}`}
|
||||||
</EuiText>
|
</span>
|
||||||
</EuiToolTip>
|
)}
|
||||||
</EuiFlexItem>
|
{bucketInterval && (
|
||||||
<EuiFlexItem grow={false}>
|
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.');
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -21,17 +21,18 @@
|
||||||
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dscPageContent {
|
.dscPageContent {
|
||||||
//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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,331 +44,381 @@ 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 = ({
|
||||||
clusterList,
|
selectedCluster,
|
||||||
clusterStatus,
|
clusterList,
|
||||||
minimize=false,
|
clusterStatus,
|
||||||
|
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: [
|
||||||
activeKey: defaultActiveKey,
|
{
|
||||||
}: {panes:[],activeKey:''};
|
key: defaultActiveKey,
|
||||||
return defaultState
|
cluster_id: defaultCluster.id,
|
||||||
}
|
title: defaultCluster.name,
|
||||||
|
},
|
||||||
const [localState, setLocalState, removeLocalState] = useLocalStorage("console:state", initialDefaultState, {
|
],
|
||||||
encode: JSON.stringify,
|
activeKey: defaultActiveKey,
|
||||||
decode: JSON.parse,
|
}
|
||||||
})
|
: { panes: [], activeKey: "" };
|
||||||
const [tabState, dispatch] = useReducer(consoleTabReducer, localState)
|
return defaultState;
|
||||||
|
|
||||||
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) {
|
||||||
return;
|
console.log("cluster not found");
|
||||||
}
|
return;
|
||||||
dispatch({
|
|
||||||
type:'add',
|
|
||||||
payload: {
|
|
||||||
cluster,
|
|
||||||
}
|
}
|
||||||
})
|
dispatch({
|
||||||
},[clusterList])
|
type: "add",
|
||||||
|
payload: {
|
||||||
const menu = (
|
cluster,
|
||||||
// <Menu onClick={newTabClick}>
|
},
|
||||||
// {(clusterList||[]).map((cluster:any)=>{
|
});
|
||||||
// return <Menu.Item key={cluster.id}>{cluster.name}</Menu.Item>
|
},
|
||||||
// })}
|
[clusterList]
|
||||||
// </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"/>
|
</div>
|
||||||
</Button>
|
<div className="tabbar-icon" onClick={fullscreenClick}>
|
||||||
</Dropdown>
|
{isFullscreen ? <RestoreIcon /> : <MaximizeIcon />}
|
||||||
</div>
|
</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,
|
||||||
right: false,
|
right: false,
|
||||||
bottom: false,
|
bottom: false,
|
||||||
left: false,
|
left: false,
|
||||||
topRight: false,
|
topRight: false,
|
||||||
bottomRight: false,
|
bottomRight: false,
|
||||||
bottomLeft: false,
|
bottomLeft: false,
|
||||||
topLeft: false,
|
topLeft: false,
|
||||||
}}>
|
}}
|
||||||
<div style={{background:'#fff', height:'100%'}}
|
>
|
||||||
onMouseOver={disableWindowScroll}
|
<div
|
||||||
onMouseOut={enableWindowScroll}
|
style={{ background: "#fff", height: "100%" }}
|
||||||
id="console"
|
onMouseOver={disableWindowScroll}
|
||||||
ref={rootRef} >
|
onMouseOut={enableWindowScroll}
|
||||||
<DraggableTabs
|
id="console"
|
||||||
onChange={onChange}
|
ref={rootRef}
|
||||||
activeKey={tabState.activeKey}
|
|
||||||
type="editable-card"
|
|
||||||
onEdit={onEdit}
|
|
||||||
hideAdd
|
|
||||||
initialOrder={tabState.order}
|
|
||||||
tabBarExtraContent={tabBarExtra}
|
|
||||||
onTabNodeMoved={onTabNodeMoved}
|
|
||||||
>
|
>
|
||||||
{panes.map(pane => (
|
<DraggableTabs
|
||||||
<TabPane tab={<TabTitle title={pane.title} onTitleChange={(title)=>{saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}>
|
onChange={onChange}
|
||||||
<TabConsole height={editorHeight - 40} selectedCluster={clusterMap[pane.cluster_id]} paneKey={pane.key} saveEditorContent={saveEditorContent} initialText={pane.content} />
|
activeKey={tabState.activeKey}
|
||||||
{/* {pane.content} */}
|
type="editable-card"
|
||||||
</TabPane>
|
onEdit={onEdit}
|
||||||
))}
|
hideAdd
|
||||||
</DraggableTabs>
|
initialOrder={tabState.order}
|
||||||
</div>
|
tabBarExtraContent={tabBarExtra}
|
||||||
</Resizable>
|
onTabNodeMoved={onTabNodeMoved}
|
||||||
|
>
|
||||||
|
{panes.map((pane) => (
|
||||||
|
<TabPane
|
||||||
|
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} */}
|
||||||
|
</TabPane>
|
||||||
|
))}
|
||||||
|
</DraggableTabs>
|
||||||
|
</div>
|
||||||
|
</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);
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue