modify discover and console
This commit is contained in:
parent
8685936eb1
commit
354bcd87bb
|
@ -5,7 +5,7 @@ import (
|
|||
log "github.com/cihub/seelog"
|
||||
httprouter "infini.sh/framework/core/api/router"
|
||||
"infini.sh/framework/core/elastic"
|
||||
"infini.sh/framework/core/metrics"
|
||||
"infini.sh/framework/core/event"
|
||||
"infini.sh/framework/core/orm"
|
||||
"infini.sh/framework/core/util"
|
||||
"net/http"
|
||||
|
@ -77,7 +77,7 @@ func (handler APIHandler) getLatestClusterMonitorData(clusterID interface{}) (ut
|
|||
]
|
||||
}`
|
||||
queryDSL := fmt.Sprintf(queryDSLTpl, clusterID)
|
||||
searchRes, err := client.SearchWithRawQueryDSL(orm.GetIndexName(metrics.MetricEvent{}), []byte(queryDSL))
|
||||
searchRes, err := client.SearchWithRawQueryDSL(orm.GetIndexName(event.Event{}), []byte(queryDSL))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
2
main.go
2
main.go
|
@ -87,7 +87,7 @@ func main() {
|
|||
}, func() {
|
||||
orm.RegisterSchemaWithIndexName(model.Dict{}, "dict")
|
||||
orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex")
|
||||
orm.RegisterSchemaWithIndexName(elastic.IndexPattern{}, "view")
|
||||
orm.RegisterSchemaWithIndexName(elastic.View{}, "view")
|
||||
orm.RegisterSchemaWithIndexName(alerting.Config{}, "alerting-config")
|
||||
orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts")
|
||||
orm.RegisterSchemaWithIndexName(alerting.AlertingHistory{}, "alerting-alert-history")
|
||||
|
|
|
@ -33,19 +33,10 @@ export default [
|
|||
// { path: '/', redirect: '/' },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/cluster/overview_new',
|
||||
name: 'overview',
|
||||
component: './Cluster/NewOverview',
|
||||
// hideInMenu: true,
|
||||
routes:[
|
||||
{ path: '/', redirect: '/' },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/cluster/overview',
|
||||
name: 'overview',
|
||||
component: './Cluster/Overview',
|
||||
component: './Cluster/NewOverview',
|
||||
// hideInMenu: true,
|
||||
routes:[
|
||||
{ path: '/', redirect: '/' },
|
||||
|
|
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;
|
||||
height: 10px;
|
||||
background: #eee;
|
||||
margin-top: -5px;
|
||||
// border-top: 1px solid #bbb;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
@ -1,24 +1,55 @@
|
|||
.ant-tabs-top-bar{
|
||||
.ant-tabs-top-bar {
|
||||
display: flex;
|
||||
}
|
||||
.ant-tabs-extra-left{
|
||||
.ant-tabs-extra-left {
|
||||
order: 1;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.ant-tabs-nav-container{
|
||||
.ant-tabs-nav-container {
|
||||
order: 2;
|
||||
flex: 0 0 auto;
|
||||
|
||||
}
|
||||
.flex-tabbar .ant-tabs-top-bar .ant-tabs-nav-container{
|
||||
max-width: calc(100% - 140px);
|
||||
.flex-tabbar {
|
||||
.ant-tabs-top-bar .ant-tabs-nav-container {
|
||||
max-width: calc(100% - 140px);
|
||||
}
|
||||
.tabbar-icon {
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tabbar-icon:hover {
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
.ant-tabs-extra-append{
|
||||
.ant-tabs-extra-append {
|
||||
order: 3;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.ant-tabs-extra-right{
|
||||
.ant-tabs-extra-right {
|
||||
order: 4;
|
||||
margin-left: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
#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 className="Console" style={{height:'100%'}}>
|
||||
<PanelsContainer resizerClassName="resizer">
|
||||
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:30 }} initialWidth={INITIAL_PANEL_WIDTH}>
|
||||
<ConsoleInput 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'}}>
|
||||
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:26 }} initialWidth={INITIAL_PANEL_WIDTH}>
|
||||
<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:26, zIndex:997, borderTop: '1px solid #eee'}}>
|
||||
<RequestStatusBar
|
||||
requestInProgress={requestInProgress}
|
||||
selectedCluster={selectedCluster}
|
||||
|
@ -78,10 +78,10 @@ const ConsoleWrapper = ({
|
|||
/>
|
||||
</div>
|
||||
</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.TabPane tab="Result" key="result">
|
||||
<div style={{height:height-30}}>
|
||||
<div style={{height:height-26}}>
|
||||
<ConsoleOutput clusterID={selectedCluster.id} />
|
||||
</div>
|
||||
|
||||
|
@ -101,7 +101,7 @@ const ConsoleWrapper = ({
|
|||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
</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
|
||||
requestInProgress={requestInProgress}
|
||||
selectedCluster={selectedCluster}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @ts-ignore
|
||||
import React, { useRef, useEffect, CSSProperties, useMemo } from 'react';
|
||||
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 { LegacyCoreEditor } from '../modules/legacy_core_editor/legacy_core_editor';
|
||||
import ConsoleMenu from './ConsoleMenu';
|
||||
|
@ -68,6 +68,7 @@ interface ConsoleInputProps {
|
|||
initialText: string | undefined,
|
||||
saveEditorContent: (content: string)=>void,
|
||||
paneKey: string,
|
||||
height?: string,
|
||||
}
|
||||
|
||||
const DEFAULT_INPUT_VALUE = `GET _search
|
||||
|
@ -78,7 +79,7 @@ const DEFAULT_INPUT_VALUE = `GET _search
|
|||
}`;
|
||||
|
||||
|
||||
const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:ConsoleInputProps) => {
|
||||
const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey, height='100%'}:ConsoleInputProps) => {
|
||||
const editorRef = useRef<HTMLDivElement | null>(null);
|
||||
const editorActionsRef = useRef<HTMLDivElement | null>(null);
|
||||
const editorInstanceRef = useRef<SenseEditor | null>(null);
|
||||
|
@ -167,7 +168,7 @@ const ConsoleInputUI = ({clusterID, initialText, saveEditorContent, paneKey}:Con
|
|||
|
||||
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">
|
||||
<ul className="conApp__autoComplete" id="autocomplete" />
|
||||
<EuiFlexGroup
|
||||
|
|
|
@ -161,17 +161,6 @@ export default class ConsoleMenu extends Component<Props, State> {
|
|||
);
|
||||
|
||||
const items = [
|
||||
<EuiContextMenuItem
|
||||
key="Copy as cURL"
|
||||
id="ConCopyAsCurl"
|
||||
disabled={!window.navigator?.clipboard}
|
||||
onClick={() => {
|
||||
this.closePopover();
|
||||
this.copyAsCurl();
|
||||
}}
|
||||
>
|
||||
复制为curl命令
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="Auto indent"
|
||||
onClick={this.autoIndent}
|
||||
|
@ -185,6 +174,19 @@ export default class ConsoleMenu extends Component<Props, State> {
|
|||
保存为常用命令
|
||||
</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 (
|
||||
<span onMouseEnter={this.mouseEnter}>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
.info-item{
|
||||
margin: 0 12px;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
&.health{
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
|
|
@ -17,12 +17,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip,EuiCodeBlock } from '@elastic/eui';
|
||||
import {HealthStatusCircle} from '@/components/infini/health_status_circle';
|
||||
import './request_status_bar.scss';
|
||||
import {Drawer, Tabs, Button} from 'antd';
|
||||
import { FormattedMessage, formatMessage } from 'umi/locale';
|
||||
import React, { FunctionComponent } from "react";
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBadge,
|
||||
EuiText,
|
||||
EuiToolTip,
|
||||
EuiCodeBlock,
|
||||
} from "@elastic/eui";
|
||||
import { HealthStatusCircle } from "@/components/infini/health_status_circle";
|
||||
import "./request_status_bar.scss";
|
||||
import { Drawer, Tabs, Button } from "antd";
|
||||
import { FormattedMessage, formatMessage } from "umi/locale";
|
||||
|
||||
export interface Props {
|
||||
requestInProgress: boolean;
|
||||
|
@ -48,22 +55,22 @@ export interface Props {
|
|||
|
||||
const mapStatusCodeToBadgeColor = (statusCode: number) => {
|
||||
if (statusCode <= 199) {
|
||||
return 'default';
|
||||
return "default";
|
||||
}
|
||||
|
||||
if (statusCode <= 299) {
|
||||
return 'secondary';
|
||||
return "secondary";
|
||||
}
|
||||
|
||||
if (statusCode <= 399) {
|
||||
return 'primary';
|
||||
return "primary";
|
||||
}
|
||||
|
||||
if (statusCode <= 499) {
|
||||
return 'warning';
|
||||
return "warning";
|
||||
}
|
||||
|
||||
return 'danger';
|
||||
return "danger";
|
||||
};
|
||||
|
||||
export const RequestStatusBar = ({
|
||||
|
@ -71,74 +78,87 @@ export const RequestStatusBar = ({
|
|||
requestResult,
|
||||
selectedCluster,
|
||||
left,
|
||||
}:Props) => {
|
||||
}: Props) => {
|
||||
let content: React.ReactNode = null;
|
||||
const clusterContent = (<div className="base-info">
|
||||
const clusterContent = (
|
||||
<div className="base-info">
|
||||
<div className="info-item health">
|
||||
<span> <FormattedMessage id="console.cluster.status"/>:</span>
|
||||
<i style={{position:'absolute', top: 1, right:0}}>
|
||||
<HealthStatusCircle status={selectedCluster.status}/>
|
||||
<span>
|
||||
{" "}
|
||||
<FormattedMessage id="console.cluster.status" />:
|
||||
</span>
|
||||
<i style={{ position: "absolute", top: 1, right: 0 }}>
|
||||
<HealthStatusCircle status={selectedCluster.status} />
|
||||
</i>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span><FormattedMessage id="console.cluster.endpoint"/>:</span>
|
||||
<EuiBadge color="default">{selectedCluster.host}</EuiBadge>
|
||||
<span>
|
||||
<FormattedMessage id="console.cluster.endpoint" />:
|
||||
</span>
|
||||
<EuiBadge color="default">{selectedCluster.host}</EuiBadge>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span><FormattedMessage id="console.cluster.version"/>:</span>
|
||||
<span>
|
||||
<FormattedMessage id="console.cluster.version" />:
|
||||
</span>
|
||||
<EuiBadge color="default">{selectedCluster.version}</EuiBadge>
|
||||
</div>
|
||||
</div>);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (requestInProgress) {
|
||||
content = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow">
|
||||
Request in progress
|
||||
</EuiBadge>
|
||||
<EuiBadge color="hollow">Request in progress</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
} else if (requestResult) {
|
||||
const { endpoint, method, statusCode, statusText, timeElapsedMs } = requestResult;
|
||||
const {
|
||||
endpoint,
|
||||
method,
|
||||
statusCode,
|
||||
statusText,
|
||||
timeElapsedMs,
|
||||
} = requestResult;
|
||||
|
||||
content = (
|
||||
<>
|
||||
<div className="status_info">
|
||||
<div className="info-item">
|
||||
<span><FormattedMessage id="console.response.status"/>:</span>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
<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={
|
||||
<div className="status_info">
|
||||
<div className="info-item">
|
||||
<span>
|
||||
<FormattedMessage id="console.response.status" />:
|
||||
</span>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
<EuiText size="s">{`${method} ${
|
||||
endpoint.startsWith("/") ? endpoint : "/" + endpoint
|
||||
}`}</EuiText>
|
||||
}
|
||||
>
|
||||
<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 size="s">
|
||||
<EuiBadge color="default">
|
||||
{timeElapsedMs} {'ms'}
|
||||
</EuiBadge>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>
|
||||
<FormattedMessage id="console.response.time_elapsed" />:
|
||||
</span>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={<EuiText size="s">Time Elapsed</EuiText>}
|
||||
>
|
||||
<EuiText size="s">
|
||||
<EuiBadge color="default">
|
||||
{timeElapsedMs} {"ms"}
|
||||
</EuiBadge>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -146,10 +166,11 @@ export const RequestStatusBar = ({
|
|||
|
||||
return (
|
||||
<div className="request-status-bar">
|
||||
{left? <div className="bar-item">{clusterContent}</div>:
|
||||
[<div className="bar-item">{content}</div>,]
|
||||
}
|
||||
{left ? (
|
||||
<div className="bar-item">{clusterContent}</div>
|
||||
) : (
|
||||
<div className="bar-item">{content}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
|
|
@ -62,7 +62,7 @@ export const useSendCurrentRequestToES = () => {
|
|||
}
|
||||
const {url, method, data} = requests[0];
|
||||
if(method === 'LOAD'){
|
||||
const rawUrl = data[0].slice(4).trim();
|
||||
const rawUrl = data[0]? data[0].slice(4).trim(): url;
|
||||
const cmd = getCommand(rawUrl);
|
||||
// const curPostion = editor.currentReqRange //(editor.getCoreEditor().getCurrentPosition());
|
||||
const lineNumber = editor.getCoreEditor().getCurrentPosition().lineNumber;
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientCommon } from '../..';
|
||||
import { SavedObjectsClientCommon } from "../..";
|
||||
|
||||
import { createIndexPatternCache } from '.';
|
||||
import { IndexPattern } from './index_pattern';
|
||||
import { createIndexPatternCache } from ".";
|
||||
import { IndexPattern } from "./index_pattern";
|
||||
import {
|
||||
createEnsureDefaultIndexPattern,
|
||||
EnsureDefaultIndexPattern,
|
||||
} from './ensure_default_index_pattern';
|
||||
} from "./ensure_default_index_pattern";
|
||||
import {
|
||||
OnNotification,
|
||||
OnError,
|
||||
|
@ -37,17 +37,17 @@ import {
|
|||
FieldSpec,
|
||||
FieldFormatMap,
|
||||
IndexPatternFieldMap,
|
||||
} from '../types';
|
||||
import { FieldFormatsStartCommon } from '../../field_formats';
|
||||
import { UI_SETTINGS, SavedObject } from '../../../common';
|
||||
import { SavedObjectNotFound } from '../../../../kibana_utils/common';
|
||||
import { IndexPatternMissingIndices } from '../lib';
|
||||
import { findByTitle } from '../utils';
|
||||
import { DuplicateIndexPatternError } from '../errors';
|
||||
} from "../types";
|
||||
import { FieldFormatsStartCommon } from "../../field_formats";
|
||||
import { UI_SETTINGS, SavedObject } from "../../../common";
|
||||
import { SavedObjectNotFound } from "../../../../kibana_utils/common";
|
||||
import { IndexPatternMissingIndices } from "../lib";
|
||||
import { findByTitle } from "../utils";
|
||||
import { DuplicateIndexPatternError } from "../errors";
|
||||
|
||||
const indexPatternCache = createIndexPatternCache();
|
||||
const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3;
|
||||
const savedObjectType = 'view';
|
||||
const savedObjectType = "view";
|
||||
|
||||
export interface IndexPatternSavedObjectAttrs {
|
||||
title: string;
|
||||
|
@ -67,7 +67,9 @@ interface IndexPatternsServiceDeps {
|
|||
export class IndexPatternsService {
|
||||
private config: UiSettingsCommon;
|
||||
private savedObjectsClient: SavedObjectsClientCommon;
|
||||
private savedObjectsCache?: Array<SavedObject<IndexPatternSavedObjectAttrs>> | null;
|
||||
private savedObjectsCache?: Array<
|
||||
SavedObject<IndexPatternSavedObjectAttrs>
|
||||
> | null;
|
||||
private apiClient: IIndexPatternsApiClient;
|
||||
private fieldFormats: FieldFormatsStartCommon;
|
||||
private onNotification: OnNotification;
|
||||
|
@ -102,9 +104,11 @@ export class IndexPatternsService {
|
|||
* Refresh cache of index pattern ids and titles
|
||||
*/
|
||||
private async refreshSavedObjectsCache() {
|
||||
this.savedObjectsCache = await this.savedObjectsClient.find<IndexPatternSavedObjectAttrs>({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
this.savedObjectsCache = await this.savedObjectsClient.find<
|
||||
IndexPatternSavedObjectAttrs
|
||||
>({
|
||||
type: "index-pattern",
|
||||
fields: ["title"],
|
||||
perPage: 10000,
|
||||
});
|
||||
}
|
||||
|
@ -180,7 +184,7 @@ export class IndexPatternsService {
|
|||
* Get default index pattern
|
||||
*/
|
||||
getDefault = async () => {
|
||||
const defaultIndexPatternId = await this.config.get('defaultIndex');
|
||||
const defaultIndexPatternId = await this.config.get("defaultIndex");
|
||||
if (defaultIndexPatternId) {
|
||||
return await this.get(defaultIndexPatternId);
|
||||
}
|
||||
|
@ -194,8 +198,8 @@ export class IndexPatternsService {
|
|||
* @param force
|
||||
*/
|
||||
setDefault = async (id: string, force = false) => {
|
||||
if (force || !this.config.get('defaultIndex')) {
|
||||
await this.config.set('defaultIndex', id);
|
||||
if (force || !this.config.get("defaultIndex")) {
|
||||
await this.config.set("defaultIndex", id);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -206,10 +210,10 @@ export class IndexPatternsService {
|
|||
|
||||
return Object.values(specs).every((spec) => {
|
||||
// See https://github.com/elastic/kibana/pull/8421
|
||||
const hasFieldCaps = 'aggregatable' in spec && 'searchable' in spec;
|
||||
const hasFieldCaps = "aggregatable" in spec && "searchable" in spec;
|
||||
|
||||
// See https://github.com/elastic/kibana/pull/11969
|
||||
const hasDocValuesFlag = 'readFromDocValues' in spec;
|
||||
const hasDocValuesFlag = "readFromDocValues" in spec;
|
||||
|
||||
return !hasFieldCaps || !hasDocValuesFlag;
|
||||
});
|
||||
|
@ -251,15 +255,22 @@ export class IndexPatternsService {
|
|||
refreshFields = async (indexPattern: IndexPattern) => {
|
||||
try {
|
||||
const fields = await this.getFieldsForIndexPattern(indexPattern);
|
||||
const scripted = indexPattern.getScriptedFields().map((field) => field.spec);
|
||||
const scripted = indexPattern
|
||||
.getScriptedFields()
|
||||
.map((field) => field.spec);
|
||||
indexPattern.fields.replaceAll([...fields, ...scripted]);
|
||||
} catch (err) {
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' });
|
||||
this.onNotification({
|
||||
title: (err as any).message,
|
||||
color: "danger",
|
||||
iconType: "alert",
|
||||
});
|
||||
}
|
||||
|
||||
this.onError(err, {
|
||||
title: 'Error fetching fields for index pattern {indexPattern.title} (ID: {indexPattern.id})',
|
||||
title:
|
||||
"Error fetching fields for index pattern {indexPattern.title} (ID: {indexPattern.id})",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -277,18 +288,24 @@ export class IndexPatternsService {
|
|||
title: string,
|
||||
options: GetFieldsOptions
|
||||
) => {
|
||||
const scriptdFields = Object.values(fields).filter((field) => field.scripted);
|
||||
const scriptdFields = Object.values(fields).filter(
|
||||
(field) => field.scripted
|
||||
);
|
||||
try {
|
||||
const newFields = await this.getFieldsForWildcard(options);
|
||||
return this.fieldArrayToMap([...newFields, ...scriptdFields]);
|
||||
} catch (err) {
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' });
|
||||
this.onNotification({
|
||||
title: (err as any).message,
|
||||
color: "danger",
|
||||
iconType: "alert",
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
this.onError(err, {
|
||||
title: `Error fetching fields for index pattern ${title} (ID: ${id})`
|
||||
title: `Error fetching fields for index pattern ${title} (ID: ${id})`,
|
||||
});
|
||||
}
|
||||
return fields;
|
||||
|
@ -299,7 +316,10 @@ export class IndexPatternsService {
|
|||
* @param fieldSpecs
|
||||
* @param fieldFormatMap
|
||||
*/
|
||||
private addFormatsToFields = (fieldSpecs: FieldSpec[], fieldFormatMap: FieldFormatMap) => {
|
||||
private addFormatsToFields = (
|
||||
fieldSpecs: FieldSpec[],
|
||||
fieldFormatMap: FieldFormatMap
|
||||
) => {
|
||||
Object.entries(fieldFormatMap).forEach(([fieldName, value]) => {
|
||||
const field = fieldSpecs.find((fld: FieldSpec) => fld.name === fieldName);
|
||||
if (field) {
|
||||
|
@ -323,7 +343,9 @@ export class IndexPatternsService {
|
|||
* @param savedObject
|
||||
*/
|
||||
|
||||
savedObjectToSpec = (savedObject: SavedObject<IndexPatternAttributes>): IndexPatternSpec => {
|
||||
savedObjectToSpec = (
|
||||
savedObject: SavedObject<IndexPatternAttributes>
|
||||
): IndexPatternSpec => {
|
||||
const {
|
||||
id,
|
||||
version,
|
||||
|
@ -336,13 +358,17 @@ export class IndexPatternsService {
|
|||
sourceFilters,
|
||||
fieldFormatMap,
|
||||
typeMeta,
|
||||
type,
|
||||
},
|
||||
type,
|
||||
} = savedObject;
|
||||
|
||||
const parsedSourceFilters = sourceFilters ? JSON.parse(sourceFilters) : undefined;
|
||||
const parsedSourceFilters = sourceFilters
|
||||
? JSON.parse(sourceFilters)
|
||||
: undefined;
|
||||
const parsedTypeMeta = typeMeta ? JSON.parse(typeMeta) : undefined;
|
||||
const parsedFieldFormatMap = fieldFormatMap ? JSON.parse(fieldFormatMap) : {};
|
||||
const parsedFieldFormatMap = fieldFormatMap
|
||||
? JSON.parse(fieldFormatMap)
|
||||
: {};
|
||||
const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : [];
|
||||
|
||||
this.addFormatsToFields(parsedFields, parsedFieldFormatMap);
|
||||
|
@ -365,16 +391,20 @@ export class IndexPatternsService {
|
|||
* @param id
|
||||
*/
|
||||
|
||||
get = async (id: string): Promise<IndexPattern> => {
|
||||
const cache = indexPatternCache.get(id);
|
||||
get = async (
|
||||
id: string,
|
||||
typ: string,
|
||||
clusterID: string
|
||||
): Promise<IndexPattern> => {
|
||||
const cacheID = typ == "index" ? clusterID + id : id;
|
||||
const cache = indexPatternCache.get(cacheID);
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const savedObject = await this.savedObjectsClient.get<IndexPatternAttributes>(
|
||||
savedObjectType,
|
||||
id
|
||||
);
|
||||
const savedObject = await this.savedObjectsClient.get<
|
||||
IndexPatternAttributes
|
||||
>(typ || savedObjectType, id);
|
||||
|
||||
// if (!savedObject.version) {
|
||||
// throw new SavedObjectNotFound(savedObjectType, id, 'management/kibana/indexPatterns');
|
||||
|
@ -382,28 +412,34 @@ export class IndexPatternsService {
|
|||
|
||||
const spec = this.savedObjectToSpec(savedObject);
|
||||
const { title, type, typeMeta } = spec;
|
||||
const parsedFieldFormats: FieldFormatMap = savedObject.attributes.fieldFormatMap
|
||||
const parsedFieldFormats: FieldFormatMap = savedObject.attributes
|
||||
.fieldFormatMap
|
||||
? JSON.parse(savedObject.attributes.fieldFormatMap)
|
||||
: {};
|
||||
|
||||
const isFieldRefreshRequired = this.isFieldRefreshRequired(spec.fields);
|
||||
const isFieldRefreshRequired =
|
||||
spec.type == "index" ? false : this.isFieldRefreshRequired(spec.fields);
|
||||
let isSaveRequired = isFieldRefreshRequired;
|
||||
try {
|
||||
spec.fields = isFieldRefreshRequired
|
||||
? await this.refreshFieldSpecMap(spec.fields || {}, id, spec.title as string, {
|
||||
pattern: title,
|
||||
metaFields: await this.config.get(UI_SETTINGS.META_FIELDS),
|
||||
type,
|
||||
params: typeMeta && typeMeta.params,
|
||||
})
|
||||
? await this.refreshFieldSpecMap(
|
||||
spec.fields || {},
|
||||
id,
|
||||
spec.title as string,
|
||||
{
|
||||
pattern: title,
|
||||
metaFields: await this.config.get(UI_SETTINGS.META_FIELDS),
|
||||
type,
|
||||
params: typeMeta && typeMeta.params,
|
||||
}
|
||||
)
|
||||
: spec.fields;
|
||||
} catch (err) {
|
||||
isSaveRequired = false;
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
this.onNotification({
|
||||
title: (err as any).message,
|
||||
color: 'danger',
|
||||
iconType: 'alert',
|
||||
color: "danger",
|
||||
iconType: "alert",
|
||||
});
|
||||
} else {
|
||||
this.onError(err, {
|
||||
|
@ -468,8 +504,13 @@ export class IndexPatternsService {
|
|||
* @param spec
|
||||
* @param skipFetchFields
|
||||
*/
|
||||
async create(spec: IndexPatternSpec, skipFetchFields = false): Promise<IndexPattern> {
|
||||
const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE);
|
||||
async create(
|
||||
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 indexPattern = new IndexPattern({
|
||||
|
@ -488,11 +529,13 @@ export class IndexPatternsService {
|
|||
}
|
||||
|
||||
find = async (search: string, size: number = 10): Promise<IndexPattern[]> => {
|
||||
const savedObjects = await this.savedObjectsClient.find<IndexPatternSavedObjectAttrs>({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
const savedObjects = await this.savedObjectsClient.find<
|
||||
IndexPatternSavedObjectAttrs
|
||||
>({
|
||||
type: "index-pattern",
|
||||
fields: ["title"],
|
||||
search,
|
||||
searchFields: ['title'],
|
||||
searchFields: ["title"],
|
||||
perPage: size,
|
||||
});
|
||||
const getIndexPatternPromises = savedObjects.map(async (savedObject) => {
|
||||
|
@ -508,7 +551,11 @@ export class IndexPatternsService {
|
|||
* @param skipFetchFields
|
||||
*/
|
||||
|
||||
async createAndSave(spec: IndexPatternSpec, override = false, skipFetchFields = false) {
|
||||
async createAndSave(
|
||||
spec: IndexPatternSpec,
|
||||
override = false,
|
||||
skipFetchFields = false
|
||||
) {
|
||||
const indexPattern = await this.create(spec, skipFetchFields);
|
||||
await this.createSavedObject(indexPattern, override);
|
||||
await this.setDefault(indexPattern.id as string);
|
||||
|
@ -527,14 +574,20 @@ export class IndexPatternsService {
|
|||
if (override) {
|
||||
await this.delete(dupe.id);
|
||||
} else {
|
||||
throw new DuplicateIndexPatternError(`Duplicate index pattern: ${indexPattern.title}`);
|
||||
throw new DuplicateIndexPatternError(
|
||||
`Duplicate index pattern: ${indexPattern.title}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const body = indexPattern.getAsSavedObjectBody();
|
||||
const response = await this.savedObjectsClient.create(savedObjectType, body, {
|
||||
id: indexPattern.id,
|
||||
});
|
||||
const response = await this.savedObjectsClient.create(
|
||||
savedObjectType,
|
||||
body,
|
||||
{
|
||||
id: indexPattern.id,
|
||||
}
|
||||
);
|
||||
indexPattern.id = response.id;
|
||||
indexPatternCache.set(indexPattern.id, indexPattern);
|
||||
return indexPattern;
|
||||
|
@ -551,6 +604,7 @@ export class IndexPatternsService {
|
|||
saveAttempts: number = 0,
|
||||
ignoreErrors: boolean = false
|
||||
): Promise<void | Error> {
|
||||
debugger;
|
||||
if (!indexPattern.id) return;
|
||||
|
||||
// get the list of attributes
|
||||
|
@ -566,13 +620,18 @@ export class IndexPatternsService {
|
|||
});
|
||||
|
||||
return this.savedObjectsClient
|
||||
.update(savedObjectType, indexPattern.id, body, { version: indexPattern.version })
|
||||
.update(savedObjectType, indexPattern.id, body, {
|
||||
version: indexPattern.version,
|
||||
})
|
||||
.then((resp) => {
|
||||
indexPattern.id = resp.id;
|
||||
indexPattern.version = resp.version;
|
||||
})
|
||||
.catch(async (err) => {
|
||||
if (err?.res?.status === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) {
|
||||
if (
|
||||
err?.res?.status === 409 &&
|
||||
saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS
|
||||
) {
|
||||
const samePattern = await this.get(indexPattern.id as string);
|
||||
// What keys changed from now and what the server returned
|
||||
const updatedBody = samePattern.getAsSavedObjectBody();
|
||||
|
@ -584,7 +643,10 @@ export class IndexPatternsService {
|
|||
|
||||
const serverChangedKeys: string[] = [];
|
||||
Object.entries(updatedBody).forEach(([key, value]) => {
|
||||
if (value !== (body as any)[key] && value !== (originalBody as any)[key]) {
|
||||
if (
|
||||
value !== (body as any)[key] &&
|
||||
value !== (originalBody as any)[key]
|
||||
) {
|
||||
serverChangedKeys.push(key);
|
||||
}
|
||||
});
|
||||
|
@ -603,10 +665,10 @@ export class IndexPatternsService {
|
|||
if (ignoreErrors) {
|
||||
return;
|
||||
}
|
||||
const title =
|
||||
'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.';
|
||||
const title =
|
||||
"Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.";
|
||||
|
||||
this.onNotification({ title, color: 'danger' });
|
||||
this.onNotification({ title, color: "danger" });
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
@ -620,7 +682,11 @@ export class IndexPatternsService {
|
|||
indexPatternCache.clear(indexPattern.id!);
|
||||
|
||||
// Try the save again
|
||||
return this.updateSavedObject(indexPattern, saveAttempts, ignoreErrors);
|
||||
return this.updateSavedObject(
|
||||
indexPattern,
|
||||
saveAttempts,
|
||||
ignoreErrors
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
|
|
@ -185,7 +185,7 @@ function FilterBarUI(props: Props) {
|
|||
alignItems="flexStart"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem className="globalFilterGroup__branch" grow={false}>
|
||||
<EuiFlexItem className="globalFilterGroup__branch" grow={false} style={{padding:0, alignSelf:'center'}}>
|
||||
<FilterOptions
|
||||
onEnableAll={onEnableAll}
|
||||
onDisableAll={onDisableAll}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { EuiIcon } from "@elastic/eui";
|
||||
import { TableHeader } from "./table_header/table_header";
|
||||
import { SortOrder } from "./table_header/helpers";
|
||||
import './_doc_table.scss';
|
||||
import {TableRow} from './table_row/table_row';
|
||||
import { useState, useEffect} from 'react';
|
||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||
import "./_doc_table.scss";
|
||||
import { TableRow } from "./table_row/table_row";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
import TableContext from "./table_context";
|
||||
|
||||
interface TableProps {
|
||||
columns: string[];
|
||||
|
@ -21,61 +22,83 @@ interface TableProps {
|
|||
|
||||
const pageCount = 50;
|
||||
|
||||
const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, onFilter, onMoveColumn, onAddColumn,
|
||||
onRemoveColumn, onChangeSortOrder, document }) => {
|
||||
const [scrollState, setScrollState] = useState({limit: pageCount, hasMore: true});
|
||||
useEffect(()=>{
|
||||
const Table: React.FC<TableProps> = ({
|
||||
columns,
|
||||
hits,
|
||||
sortOrder,
|
||||
indexPattern,
|
||||
onFilter,
|
||||
onMoveColumn,
|
||||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
onChangeSortOrder,
|
||||
document,
|
||||
}) => {
|
||||
const [scrollState, setScrollState] = useState({
|
||||
limit: pageCount,
|
||||
hasMore: true,
|
||||
});
|
||||
useEffect(() => {
|
||||
setScrollState({
|
||||
limit: pageCount,
|
||||
limit: pageCount,
|
||||
hasMore: hits.length > pageCount,
|
||||
})
|
||||
},[indexPattern, hits])
|
||||
|
||||
});
|
||||
}, [indexPattern, hits]);
|
||||
const tableRef = React.useRef(null);
|
||||
|
||||
return (
|
||||
<InfiniteScroll
|
||||
dataLength={scrollState.limit}
|
||||
next={()=>{
|
||||
next={() => {
|
||||
const newLimit = scrollState.limit + pageCount;
|
||||
setScrollState({limit: newLimit, hasMore: newLimit < hits.length});
|
||||
setScrollState({ limit: newLimit, hasMore: newLimit < hits.length });
|
||||
}}
|
||||
hasMore={scrollState.hasMore}
|
||||
loader={<h4 style={{textAlign: 'center', margin: '10px auto'}}>Loading...</h4>}
|
||||
loader={
|
||||
<h4 style={{ textAlign: "center", margin: "10px auto" }}>Loading...</h4>
|
||||
}
|
||||
endMessage={
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
{/* <b>no more data</b> */}
|
||||
</p>
|
||||
}>
|
||||
<div>
|
||||
<p style={{ textAlign: "center" }}>{/* <b>no more data</b> */}</p>
|
||||
}
|
||||
>
|
||||
<div ref={tableRef}>
|
||||
{hits.length ? (
|
||||
<div>
|
||||
<table className="kbn-table table">
|
||||
<thead>
|
||||
<TableHeader columns={columns}
|
||||
defaultSortOrder={''}
|
||||
hideTimeColumn={false}
|
||||
indexPattern={indexPattern}
|
||||
isShortDots={false}
|
||||
onChangeSortOrder={onChangeSortOrder}
|
||||
onMoveColumn={onMoveColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
sortOrder={sortOrder||[]}/>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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}
|
||||
<TableContext.Provider value={{ tableRef: tableRef.current }}>
|
||||
<table className="kbn-table table">
|
||||
<thead>
|
||||
<TableHeader
|
||||
columns={columns}
|
||||
defaultSortOrder={"desc"}
|
||||
hideTimeColumn={false}
|
||||
indexPattern={indexPattern}
|
||||
isShortDots={false}
|
||||
onChangeSortOrder={onChangeSortOrder}
|
||||
onMoveColumn={onMoveColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
sortOrder={sortOrder || []}
|
||||
/>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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>
|
||||
) : null}
|
||||
|
||||
|
@ -89,8 +112,8 @@ const Table: React.FC<TableProps> = ({ columns, hits, sortOrder, indexPattern, o
|
|||
</div>
|
||||
) : null}
|
||||
</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
|
||||
.kbnDocTableDetails__row {
|
||||
|
||||
> td {
|
||||
// Offsets negative margins from an inner flex group
|
||||
padding: 16px !important;
|
||||
|
@ -22,4 +21,3 @@
|
|||
border-top: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
import {EuiIcon} from '@elastic/eui';
|
||||
import { DocViewer } from '../../doc_viewer/doc_viewer';
|
||||
import {Drawer, Button, Menu,Dropdown, Icon, Popconfirm, message,Descriptions, Popover, Input} from 'antd';
|
||||
import { EuiIcon } from "@elastic/eui";
|
||||
import { DocViewer } from "../../doc_viewer/doc_viewer";
|
||||
import {
|
||||
Drawer,
|
||||
Button,
|
||||
Menu,
|
||||
Dropdown,
|
||||
Icon,
|
||||
Popconfirm,
|
||||
message,
|
||||
Descriptions,
|
||||
Popover,
|
||||
Input,
|
||||
} from "antd";
|
||||
import Editor from "@monaco-editor/react";
|
||||
import {useState, useRef} from 'react';
|
||||
import { useState, useRef } from "react";
|
||||
|
||||
function generateNewID(id: string) {
|
||||
return id.slice(0, 14) + Math.random().toString(36).substr(2, 6)
|
||||
return (
|
||||
id.slice(0, 14) +
|
||||
Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 6)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface Props {
|
||||
columns: string[];
|
||||
indexPattern: any;
|
||||
|
@ -28,28 +42,28 @@ export function Detail({
|
|||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
document,
|
||||
}:Props){
|
||||
}: Props) {
|
||||
const [editorVisible, setEditorVisble] = useState(false);
|
||||
const editorRef = useRef(null);
|
||||
|
||||
function handleEditorDidMount(editor, monaco) {
|
||||
editorRef.current = editor;
|
||||
editorRef.current = editor;
|
||||
}
|
||||
|
||||
const editDocumentClick = ()=>{
|
||||
setEditorVisble(true)
|
||||
}
|
||||
const editCancelClick = ()=>{
|
||||
setEditorVisble(false)
|
||||
}
|
||||
const saveDocumentClick = async (docID?: string)=>{
|
||||
const editDocumentClick = () => {
|
||||
setEditorVisble(true);
|
||||
};
|
||||
const editCancelClick = () => {
|
||||
setEditorVisble(false);
|
||||
};
|
||||
const saveDocumentClick = async (docID?: string) => {
|
||||
const value = editorRef.current?.getValue();
|
||||
let source = {}
|
||||
let source = {};
|
||||
try {
|
||||
source = JSON.parse(value)
|
||||
source = JSON.parse(value);
|
||||
} catch (error) {
|
||||
message.error('wrong json format')
|
||||
return
|
||||
message.error("wrong json format");
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
_index: row._index,
|
||||
|
@ -57,18 +71,18 @@ export function Detail({
|
|||
_type: row._type,
|
||||
_source: source,
|
||||
};
|
||||
|
||||
docID && (params['is_new'] = '1')
|
||||
const res = await document.saveDocument(params)
|
||||
if(!res.error) setEditorVisble(false)
|
||||
}
|
||||
const deleteDocumentClick = ()=>{
|
||||
|
||||
docID && (params["is_new"] = "1");
|
||||
const res = await document.saveDocument(params);
|
||||
if (!res.error) setEditorVisble(false);
|
||||
};
|
||||
const deleteDocumentClick = () => {
|
||||
document.deleteDocument({
|
||||
_index: row._index,
|
||||
_id: row._id,
|
||||
_type: row._type,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
|
@ -76,72 +90,117 @@ export function Detail({
|
|||
<a> Edit </a>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="Delete">
|
||||
<Popconfirm title="sure to delete" onConfirm={()=>{
|
||||
deleteDocumentClick();
|
||||
}}><a> Delete </a></Popconfirm>
|
||||
<Popconfirm
|
||||
title="sure to delete"
|
||||
onConfirm={() => {
|
||||
deleteDocumentClick();
|
||||
}}
|
||||
>
|
||||
<a> Delete </a>
|
||||
</Popconfirm>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<td colSpan={ columns.length + 2 }>
|
||||
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween">
|
||||
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||
<div className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow">
|
||||
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
||||
<EuiIcon type="folderOpen" size="m" style={{marginTop: 5}}/>
|
||||
</div>
|
||||
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
||||
<h4
|
||||
data-test-subj="docTableRowDetailsTitle"
|
||||
className="euiTitle euiTitle--xsmall"
|
||||
>Expanded document</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||
<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>
|
||||
<td
|
||||
colSpan={columns.length + 2}
|
||||
style={{
|
||||
wordBreak: "break-all",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
>
|
||||
<div className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--justifyContentSpaceBetween">
|
||||
<div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||
<div className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow">
|
||||
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
||||
<EuiIcon type="folderOpen" size="m" style={{ marginTop: 5 }} />
|
||||
</div>
|
||||
<div className="euiFlexItem euiFlexItem--flexGrowZero">
|
||||
<h4
|
||||
data-test-subj="docTableRowDetailsTitle"
|
||||
className="euiTitle euiTitle--xsmall"
|
||||
>
|
||||
Expanded document
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
<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"
|
||||
onClick={()=>{setEditorVisble(true)}}
|
||||
>Edit document</a> */}
|
||||
<Dropdown overlay={menu} >
|
||||
<a className="ant-dropdown-link" onClick={e => e.preventDefault()}>
|
||||
Operation <Icon type="down" />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||
<Dropdown overlay={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
Operation <Icon type="down" />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero euiText euiText--small">
|
||||
<a
|
||||
className="euiLink"
|
||||
>View surrounding documents</a>
|
||||
|
@ -151,45 +210,58 @@ export function Detail({
|
|||
className="euiLink"
|
||||
>View single document</a>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-test-subj="docViewer">
|
||||
<DocViewer
|
||||
columns={columns}
|
||||
filter={onFilter}
|
||||
hit={row}
|
||||
indexPattern={indexPattern}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
|
||||
)
|
||||
<div data-test-subj="docViewer">
|
||||
<DocViewer
|
||||
columns={columns}
|
||||
filter={onFilter}
|
||||
hit={row}
|
||||
indexPattern={indexPattern}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
const SaveAsNewButton = ({docID, saveDocumentClick}:any)=>{
|
||||
const SaveAsNewButton = ({ docID, saveDocumentClick }: any) => {
|
||||
const newID = generateNewID(docID);
|
||||
const [newDocID, setNewDocID] = useState(newID)
|
||||
const content = (<div style={{width: 200}}>
|
||||
<div><Input value={newDocID} onChange={(e)=>{
|
||||
setNewDocID(e.target.value)
|
||||
}} /></div>
|
||||
<div style={{marginTop:10}}><Button onClick={()=>{
|
||||
saveDocumentClick(newDocID)
|
||||
}}>确定</Button></div>
|
||||
</div>)
|
||||
const [newDocID, setNewDocID] = useState(newID);
|
||||
const content = (
|
||||
<div style={{ width: 200 }}>
|
||||
<div>
|
||||
<Input
|
||||
value={newDocID}
|
||||
onChange={(e) => {
|
||||
setNewDocID(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
saveDocumentClick(newDocID);
|
||||
}}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
title="Please input new ID"
|
||||
trigger="click"
|
||||
// visible={this.state.visible}
|
||||
// onVisibleChange={this.handleVisibleChange}
|
||||
>
|
||||
<Button style={{marginRight:5}} type="primary">Save as new</Button>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
content={content}
|
||||
title="Please input new ID"
|
||||
trigger="click"
|
||||
// visible={this.state.visible}
|
||||
// onVisibleChange={this.handleVisibleChange}
|
||||
>
|
||||
<Button style={{ marginRight: 5 }} type="primary">
|
||||
Save as new
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import {Open} from './open';
|
||||
import {Cell} from './cell';
|
||||
import {Detail} from './detail';
|
||||
import {useState} from 'react';
|
||||
import { Open } from "./open";
|
||||
import { Cell } from "./cell";
|
||||
import { Detail } from "./detail";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const MemoDetail = React.memo(Detail);
|
||||
|
||||
interface Props {
|
||||
// sorting="sorting"
|
||||
|
@ -25,62 +27,89 @@ export function TableRow({
|
|||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
row,
|
||||
document
|
||||
}:Props){
|
||||
document,
|
||||
}: Props) {
|
||||
const mapping = indexPattern.fields.getByName;
|
||||
const [open,setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
className="kbnDocTable__row"
|
||||
>
|
||||
<Open open={open} onClick={()=>{
|
||||
setOpen(!open);
|
||||
}}/>
|
||||
{(indexPattern.timeFieldName && !hideTimeColumn)? <Cell timefield={true}
|
||||
row={row}
|
||||
indexPattern={indexPattern}
|
||||
inlineFilter={onFilter}
|
||||
formatted={_displayField(indexPattern, row, indexPattern.timeFieldName)}
|
||||
filterable={mapping(indexPattern.timeFieldName).filterable} //&& $scope.filter
|
||||
column= {indexPattern.timeFieldName}/>: null}
|
||||
<tr className="kbnDocTable__row">
|
||||
<Open
|
||||
open={open}
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
/>
|
||||
{indexPattern.timeFieldName && !hideTimeColumn ? (
|
||||
<Cell
|
||||
timefield={true}
|
||||
row={row}
|
||||
indexPattern={indexPattern}
|
||||
inlineFilter={onFilter}
|
||||
formatted={_displayField(
|
||||
indexPattern,
|
||||
row,
|
||||
indexPattern.timeFieldName
|
||||
)}
|
||||
filterable={mapping(indexPattern.timeFieldName).filterable} //&& $scope.filter
|
||||
column={indexPattern.timeFieldName}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{columns.map(function (column: any) {
|
||||
const isFilterable = mapping(column) && mapping(column).filterable ;//&& $scope.filter;
|
||||
return <Cell key={'discover-cell-'+column} timefield={false}
|
||||
row={row}
|
||||
inlineFilter={onFilter}
|
||||
indexPattern={indexPattern}
|
||||
sourcefield={column === '_source'}
|
||||
formatted={_displayField(indexPattern, row, column, true)}
|
||||
filterable={isFilterable} //&& $scope.filter
|
||||
column= {column}/>
|
||||
{columns.map(function(column: any) {
|
||||
const isFilterable = mapping(column) && mapping(column).filterable; //&& $scope.filter;
|
||||
return (
|
||||
<Cell
|
||||
key={"discover-cell-" + column}
|
||||
timefield={false}
|
||||
row={row}
|
||||
inlineFilter={onFilter}
|
||||
indexPattern={indexPattern}
|
||||
sourcefield={column === "_source"}
|
||||
formatted={_displayField(indexPattern, row, column, true)}
|
||||
filterable={isFilterable} //&& $scope.filter
|
||||
column={column}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
|
||||
{open? <tr className="kbnDocTableDetails__row">
|
||||
<Detail columns={columns}
|
||||
indexPattern={indexPattern}
|
||||
row={row}
|
||||
document={document}
|
||||
onFilter={onFilter}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}/>
|
||||
</tr>:null
|
||||
}
|
||||
</tr>
|
||||
|
||||
{open && (
|
||||
<tr className="kbnDocTableDetails__row">
|
||||
<MemoDetail
|
||||
columns={columns}
|
||||
indexPattern={indexPattern}
|
||||
row={row}
|
||||
document={document}
|
||||
onFilter={onFilter}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
/>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const MIN_LINE_LENGTH = 20;
|
||||
|
||||
function _displayField(indexPattern:any, row: any, fieldName: string, truncate = false) {
|
||||
function _displayField(
|
||||
indexPattern: any,
|
||||
row: any,
|
||||
fieldName: string,
|
||||
truncate = false
|
||||
) {
|
||||
const text = indexPattern.formatField(row, fieldName);
|
||||
|
||||
if (truncate && text.length > MIN_LINE_LENGTH) {
|
||||
return <div className="truncate-by-height" dangerouslySetInnerHTML={{ __html: text }} >
|
||||
</div>
|
||||
return (
|
||||
<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
|
||||
* under the License.
|
||||
*/
|
||||
import './doc_viewer.scss';
|
||||
import React from 'react';
|
||||
import { EuiTabbedContent } from '@elastic/eui';
|
||||
import { getDocViewsRegistry } from '../../../kibana_services';
|
||||
import { DocViewerTab } from './doc_viewer_tab';
|
||||
import { DocView, DocViewRenderProps } from '../../doc_views/doc_views_types';
|
||||
import "./doc_viewer.scss";
|
||||
import React from "react";
|
||||
import { EuiTabbedContent } from "@elastic/eui";
|
||||
import { getDocViewsRegistry } from "../../../kibana_services";
|
||||
import { DocViewerTab } from "./doc_viewer_tab";
|
||||
import { DocView, DocViewRenderProps } from "../../doc_views/doc_views_types";
|
||||
import TableContext from "../discover_table/table_context";
|
||||
|
||||
/**
|
||||
* Rendering tabs with different views of 1 Elasticsearch hit in Discover.
|
||||
|
@ -54,9 +55,23 @@ export function DocViewer(renderProps: DocViewRenderProps) {
|
|||
// This condition takes care of unit tests with 0 tabs.
|
||||
return null;
|
||||
}
|
||||
const { tableRef } = React.useContext(TableContext);
|
||||
const [viewerWidth, setViewerWidth] = React.useState(
|
||||
tableRef?.offsetWidth - 40
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const onResize = () => {
|
||||
setViewerWidth(tableRef?.offsetWidth - 40);
|
||||
};
|
||||
window.addEventListener("resize", onResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="kbnDocViewer">
|
||||
<div className="kbnDocViewer" style={{ width: viewerWidth }}>
|
||||
<EuiTabbedContent tabs={tabs} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import moment, { unitOfTime } from 'moment-timezone';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment, { unitOfTime } from "moment-timezone";
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import {
|
||||
Axis,
|
||||
|
@ -15,28 +15,27 @@ import {
|
|||
BrushEndListener,
|
||||
Theme,
|
||||
LIGHT_THEME,
|
||||
} from '@elastic/charts';
|
||||
import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
} from "@elastic/charts";
|
||||
import lightEuiTheme from "@elastic/eui/dist/eui_theme_light.json";
|
||||
import darkEuiTheme from "@elastic/eui/dist/eui_theme_dark.json";
|
||||
import "@elastic/charts/dist/theme_light.css";
|
||||
|
||||
import { Subscription, combineLatest } from 'rxjs';
|
||||
import {CurrentTime} from './current_time';
|
||||
import { Subscription, combineLatest } from "rxjs";
|
||||
import { CurrentTime } from "./current_time";
|
||||
import {
|
||||
Endzones,
|
||||
getAdjustedInterval,
|
||||
renderEndzoneTooltip,
|
||||
} from './endzones';
|
||||
|
||||
} from "./endzones";
|
||||
|
||||
function getTimezone() {
|
||||
const detectedTimezone = moment.tz.guess();
|
||||
if (detectedTimezone) return detectedTimezone;
|
||||
else return moment().format('Z');
|
||||
else return moment().format("Z");
|
||||
}
|
||||
|
||||
export class DiscoverHistogram extends Component{
|
||||
static propTypes = {
|
||||
export class DiscoverHistogram extends Component {
|
||||
static propTypes = {
|
||||
chartData: PropTypes.object,
|
||||
timefilterUpdateHandler: PropTypes.func,
|
||||
};
|
||||
|
@ -62,7 +61,7 @@ export class DiscoverHistogram extends Component{
|
|||
}
|
||||
}
|
||||
|
||||
onBrushEnd = ({ x }) => {
|
||||
onBrushEnd = ({ x }) => {
|
||||
if (!x) {
|
||||
return;
|
||||
}
|
||||
|
@ -71,7 +70,7 @@ export class DiscoverHistogram extends Component{
|
|||
};
|
||||
|
||||
onElementClick = (xInterval) => ([elementData]) => {
|
||||
const startRange = (elementData)[0].x;
|
||||
const startRange = elementData[0].x;
|
||||
|
||||
const range = {
|
||||
from: startRange,
|
||||
|
@ -130,28 +129,33 @@ export class DiscoverHistogram extends Component{
|
|||
),
|
||||
};
|
||||
const tooltipProps = {
|
||||
headerFormatter: renderEndzoneTooltip(xInterval, domainStart, domainEnd, this.formatXValue),
|
||||
headerFormatter: renderEndzoneTooltip(
|
||||
xInterval,
|
||||
domainStart,
|
||||
domainEnd,
|
||||
this.formatXValue
|
||||
),
|
||||
type: TooltipType.VerticalCursor,
|
||||
};
|
||||
// const xAxisFormatter = getServices().data.fieldFormats.deserialize(
|
||||
// this.props.chartData.yAxisFormat
|
||||
// );
|
||||
const xAxisFormatter = {
|
||||
convert: (value)=>{
|
||||
convert: (value) => {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
//console.log(data)
|
||||
|
||||
return (
|
||||
<Chart size="100%" size={{height:200}}>
|
||||
<Chart size="100%" size={{ height: 120 }}>
|
||||
<Settings
|
||||
xDomain={xDomain}
|
||||
onBrushEnd={this.onBrushEnd}
|
||||
onElementClick={this.onElementClick(xInterval)}
|
||||
tooltip={tooltipProps}
|
||||
theme={LIGHT_THEME}
|
||||
// baseTheme={chartsBaseTheme}
|
||||
// baseTheme={chartsBaseTheme}
|
||||
/>
|
||||
<Axis
|
||||
id="discover-histogram-left-axis"
|
||||
|
@ -159,13 +163,15 @@ export class DiscoverHistogram extends Component{
|
|||
ticks={5}
|
||||
title={chartData.yAxisLabel}
|
||||
integersOnly
|
||||
tickFormat={(value) => {return xAxisFormatter.convert(value)}}
|
||||
tickFormat={(value) => {
|
||||
return xAxisFormatter.convert(value);
|
||||
}}
|
||||
showGridLines
|
||||
/>
|
||||
<Axis
|
||||
id="discover-histogram-bottom-axis"
|
||||
position={Position.Bottom}
|
||||
title={chartData.xAxisLabel}
|
||||
// title={chartData.xAxisLabel}
|
||||
tickFormat={this.formatXValue}
|
||||
ticks={10}
|
||||
//showGridLines
|
||||
|
@ -185,12 +191,11 @@ export class DiscoverHistogram extends Component{
|
|||
xScaleType={ScaleType.Time}
|
||||
yScaleType={ScaleType.Linear}
|
||||
xAccessor="x"
|
||||
yAccessors={['y']}
|
||||
yAccessors={["y"]}
|
||||
data={data}
|
||||
timeZone={timeZone}
|
||||
name={chartData.yAxisLabel}
|
||||
/>
|
||||
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiCodeBlock } from '@elastic/eui';
|
||||
import { DocViewRenderProps } from '../../doc_views/doc_views_types';
|
||||
import React from "react";
|
||||
import { EuiCodeBlock } from "@elastic/eui";
|
||||
import { DocViewRenderProps } from "../../doc_views/doc_views_types";
|
||||
|
||||
export function JsonCodeBlock({ hit }: DocViewRenderProps) {
|
||||
const label = 'Read only JSON view of an elasticsearch document';
|
||||
const label = "Read only JSON view of an elasticsearch document";
|
||||
return (
|
||||
<EuiCodeBlock aria-label={label} language="json" isCopyable paddingSize="s">
|
||||
{JSON.stringify(hit, null, 2)}
|
||||
|
|
|
@ -24,29 +24,34 @@ import {
|
|||
EuiPopoverTitle,
|
||||
EuiSelectable,
|
||||
EuiButtonEmptyProps,
|
||||
EuiTabs,
|
||||
EuiTabbedContent,
|
||||
EuiTab,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable';
|
||||
import { IndexPatternRef } from './types';
|
||||
|
||||
|
||||
export type ChangeIndexPatternTriggerProps = EuiButtonEmptyProps & {
|
||||
label: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
// TODO: refactor to shared component with ../../../../../../../../x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern
|
||||
|
||||
export function ChangeIndexPattern({
|
||||
indexPatternRefs,
|
||||
indexPatternId,
|
||||
onChangeIndexPattern,
|
||||
trigger,
|
||||
selectableProps,
|
||||
indices,
|
||||
}: {
|
||||
trigger: ChangeIndexPatternTriggerProps;
|
||||
indexPatternRefs: IndexPatternRef[];
|
||||
onChangeIndexPattern: (newId: string) => void;
|
||||
onChangeIndexPattern: (newId: string, typ: string) => void;
|
||||
indexPatternId?: string;
|
||||
selectableProps?: EuiSelectableProps;
|
||||
indices: string[];
|
||||
}) {
|
||||
const [isPopoverOpen, setPopoverIsOpen] = useState(false);
|
||||
|
||||
|
@ -68,22 +73,21 @@ export function ChangeIndexPattern({
|
|||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<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>
|
||||
<EuiSelectable
|
||||
const [selectedTabId, setSelectedTabId] = useState(indices.includes(indexPatternId) ? 1 :0);
|
||||
const onSelectedTabChanged = (id: number) => {
|
||||
setSelectedTabId(id);
|
||||
};
|
||||
const [includeSystemIndex, setIncludeSystemIndex] = useState(false);
|
||||
|
||||
const tabs = React.useMemo(()=>{
|
||||
const showIndices = includeSystemIndex ? indices: indices.filter(key=>!key.startsWith("."));
|
||||
const tabs = [
|
||||
{
|
||||
id: 'view',
|
||||
name: 'View',
|
||||
disabled: false,
|
||||
content: ( <EuiSelectable
|
||||
style={{marginTop:10}}
|
||||
data-test-subj="indexPattern-switcher"
|
||||
{...selectableProps}
|
||||
searchable
|
||||
|
@ -98,7 +102,7 @@ export function ChangeIndexPattern({
|
|||
const choice = (choices.find(({ checked }) => checked) as unknown) as {
|
||||
value: string;
|
||||
};
|
||||
onChangeIndexPattern(choice.value);
|
||||
onChangeIndexPattern(choice.value, 'view');
|
||||
setPopoverIsOpen(false);
|
||||
}}
|
||||
searchProps={{
|
||||
|
@ -112,7 +116,103 @@ export function ChangeIndexPattern({
|
|||
{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>
|
||||
</EuiPopover>
|
||||
);
|
||||
|
|
|
@ -16,15 +16,20 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { EuiPopover, EuiPopoverTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { DiscoverFieldDetails } from './discover_field_details';
|
||||
import { FieldIcon, FieldButton } from '../../../../../kibana_react/public';
|
||||
import { FieldDetails } from './types';
|
||||
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
|
||||
import { shortenDottedString } from '../../helpers';
|
||||
import { getFieldTypeName } from './lib/get_field_type_name';
|
||||
import './discover_field.scss';
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiButtonIcon,
|
||||
EuiToolTip,
|
||||
} from "@elastic/eui";
|
||||
import { DiscoverFieldDetails } from "./discover_field_details";
|
||||
import { FieldIcon, FieldButton } from "../../../../../kibana_react/public";
|
||||
import { FieldDetails } from "./types";
|
||||
import { IndexPatternField, IndexPattern } from "../../../../../data/public";
|
||||
import { shortenDottedString } from "../../helpers";
|
||||
import { getFieldTypeName } from "./lib/get_field_type_name";
|
||||
import "./discover_field.scss";
|
||||
|
||||
export interface DiscoverFieldProps {
|
||||
/**
|
||||
|
@ -42,7 +47,11 @@ export interface DiscoverFieldProps {
|
|||
/**
|
||||
* Callback to add a filter to filter bar
|
||||
*/
|
||||
onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
onAddFilter: (
|
||||
field: IndexPatternField | string,
|
||||
value: string,
|
||||
type: "+" | "-"
|
||||
) => void;
|
||||
/**
|
||||
* Callback to remove/deselect a the field
|
||||
* @param fieldName
|
||||
|
@ -72,8 +81,8 @@ export function DiscoverField({
|
|||
selected,
|
||||
useShortDots,
|
||||
}: DiscoverFieldProps) {
|
||||
const addLabelAria = `Add ${field.name } to table`;
|
||||
const removeLabelAria = `Remove ${field.name } to table`;
|
||||
const addLabelAria = `Add ${field.name} to table`;
|
||||
const removeLabelAria = `Remove ${field.name} to table`;
|
||||
|
||||
const [infoIsOpen, setOpen] = useState(false);
|
||||
|
||||
|
@ -93,11 +102,15 @@ export function DiscoverField({
|
|||
// u200B is a non-width white-space character, which allows
|
||||
// the browser to efficiently word-wrap right after the dot
|
||||
// without us having to draw a lot of extra DOM elements, etc
|
||||
return str ? str.replace(/\./g, '.\u200B') : '';
|
||||
return str ? str.replace(/\./g, ".\u200B") : "";
|
||||
}
|
||||
|
||||
const dscFieldIcon = (
|
||||
<FieldIcon type={field.type} label={getFieldTypeName(field.type)} scripted={field.scripted} />
|
||||
<FieldIcon
|
||||
type={field.type}
|
||||
label={getFieldTypeName(field.type)}
|
||||
scripted={field.scripted}
|
||||
/>
|
||||
);
|
||||
|
||||
const fieldName = (
|
||||
|
@ -106,22 +119,21 @@ export function DiscoverField({
|
|||
title={field.name}
|
||||
className="dscSidebarField__name"
|
||||
>
|
||||
{useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
|
||||
{useShortDots
|
||||
? wrapOnDot(shortenDottedString(field.name))
|
||||
: wrapOnDot(field.displayName)}
|
||||
</span>
|
||||
);
|
||||
|
||||
let actionButton;
|
||||
if (field.name !== '_source' && !selected) {
|
||||
if (field.name !== "_source" && !selected) {
|
||||
actionButton = (
|
||||
<EuiToolTip
|
||||
delay="long"
|
||||
content={ 'Add field as column'}
|
||||
>
|
||||
<EuiToolTip delay="long" content={"Add field as column"}>
|
||||
<EuiButtonIcon
|
||||
iconType="plusInCircleFilled"
|
||||
className="dscSidebarItem__action"
|
||||
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (ev.type === 'click') {
|
||||
if (ev.type === "click") {
|
||||
ev.currentTarget.focus();
|
||||
}
|
||||
ev.preventDefault();
|
||||
|
@ -133,18 +145,15 @@ export function DiscoverField({
|
|||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
} else if (field.name !== '_source' && selected) {
|
||||
} else if (field.name !== "_source" && selected) {
|
||||
actionButton = (
|
||||
<EuiToolTip
|
||||
delay="long"
|
||||
content={ 'Remove field from table'}
|
||||
>
|
||||
<EuiToolTip delay="long" content={"Remove field from table"}>
|
||||
<EuiButtonIcon
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
className="dscSidebarItem__action"
|
||||
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (ev.type === 'click') {
|
||||
if (ev.type === "click") {
|
||||
ev.currentTarget.focus();
|
||||
}
|
||||
ev.preventDefault();
|
||||
|
@ -158,7 +167,7 @@ export function DiscoverField({
|
|||
);
|
||||
}
|
||||
|
||||
if (field.type === '_source') {
|
||||
if (field.type === "_source") {
|
||||
return (
|
||||
<FieldButton
|
||||
size="s"
|
||||
|
@ -194,10 +203,7 @@ export function DiscoverField({
|
|||
anchorPosition="rightUp"
|
||||
panelClassName="dscSidebarItem__fieldPopoverPanel"
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
{' '}
|
||||
{ 'Top 5 values'}
|
||||
</EuiPopoverTitle>
|
||||
<EuiPopoverTitle> {"Top 5 values"}</EuiPopoverTitle>
|
||||
{infoIsOpen && (
|
||||
<DiscoverFieldDetails
|
||||
indexPattern={indexPattern}
|
||||
|
|
|
@ -34,7 +34,8 @@ export interface DiscoverIndexPatternProps {
|
|||
/**
|
||||
* 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,
|
||||
selectedIndexPattern,
|
||||
setIndexPattern,
|
||||
indices,
|
||||
}: DiscoverIndexPatternProps) {
|
||||
const options: IndexPatternRef[] = (indexPatternList || []).map((entity) => ({
|
||||
id: entity.id,
|
||||
|
@ -75,13 +77,27 @@ export function DiscoverIndexPattern({
|
|||
}}
|
||||
indexPatternId={selected.id}
|
||||
indexPatternRefs={options}
|
||||
onChangeIndexPattern={(id) => {
|
||||
const indexPattern = options.find((pattern) => pattern.id === id);
|
||||
onChangeIndexPattern={(id, typ) => {
|
||||
let indexPattern = null;
|
||||
if(typ == 'index'){
|
||||
indices.forEach((indexName)=>{
|
||||
if(indexName == id){
|
||||
indexPattern = {
|
||||
id: indexName,
|
||||
title: indexName,
|
||||
viewName: indexName,
|
||||
}
|
||||
}
|
||||
})
|
||||
}else{
|
||||
indexPattern = options.find((pattern) => pattern.id === id);
|
||||
}
|
||||
if (indexPattern) {
|
||||
setIndexPattern(id);
|
||||
setIndexPattern(id, typ);
|
||||
setSelected(indexPattern);
|
||||
}
|
||||
}}
|
||||
indices={indices}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../../../../core/public/variables.scss';
|
||||
@import "../../../../../core/public/variables.scss";
|
||||
.dscSidebar__container {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
|
@ -26,11 +26,17 @@
|
|||
|
||||
.dscFieldListHeader {
|
||||
padding: $euiSizeS $euiSizeS 0 $euiSizeS;
|
||||
background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade);
|
||||
background-color: lightOrDarkTheme(
|
||||
tint($euiColorPrimary, 90%),
|
||||
$euiColorLightShade
|
||||
);
|
||||
}
|
||||
|
||||
.dscFieldList--popular {
|
||||
background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade);
|
||||
background-color: lightOrDarkTheme(
|
||||
tint($euiColorPrimary, 90%),
|
||||
$euiColorLightShade
|
||||
);
|
||||
}
|
||||
|
||||
.dscFieldChooser {
|
||||
|
@ -45,7 +51,7 @@
|
|||
.dscSidebarItem {
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&[class*='-isActive'] {
|
||||
&[class*="-isActive"] {
|
||||
.dscSidebarItem__action {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -95,3 +101,23 @@
|
|||
color: $euiTextColor;
|
||||
margin-bottom: $euiSizeS;
|
||||
}
|
||||
|
||||
#fields-tree-wrapper {
|
||||
.ant-tree-switcher-noop {
|
||||
display: none;
|
||||
}
|
||||
.ant-tree-treenode-switcher-close {
|
||||
padding: 0;
|
||||
.ant-tree-node-content-wrapper {
|
||||
height: 32px;
|
||||
}
|
||||
.ant-tree-title {
|
||||
display: block;
|
||||
height: 32px;
|
||||
.kbnFieldButton__button {
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,22 +16,27 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import './discover_sidebar.scss';
|
||||
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { EuiButtonIcon, EuiTitle, EuiSpacer,EuiHideFor } from '@elastic/eui';
|
||||
import { sortBy } from 'lodash';
|
||||
import { DiscoverField } from './discover_field';
|
||||
import { DiscoverIndexPattern } from './discover_index_pattern';
|
||||
import { DiscoverFieldSearch } from './discover_field_search';
|
||||
import { IndexPatternAttributes } from '../../../../../data/common';
|
||||
import { SavedObject } from '../../../../../../core/types';
|
||||
import "./discover_sidebar.scss";
|
||||
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
||||
import { EuiButtonIcon, EuiTitle, EuiSpacer, EuiHideFor } from "@elastic/eui";
|
||||
import { sortBy } from "lodash";
|
||||
import { DiscoverField } from "./discover_field";
|
||||
import { DiscoverIndexPattern } from "./discover_index_pattern";
|
||||
import { DiscoverFieldSearch } from "./discover_field_search";
|
||||
import { IndexPatternAttributes } from "../../../../../data/common";
|
||||
import { SavedObject } from "../../../../../../core/types";
|
||||
// import { FIELDS_LIMIT_SETTING } from '../../../../common';
|
||||
import { groupFields } from './lib/group_fields';
|
||||
import { IndexPatternField, IndexPattern, UI_SETTINGS } from '../../../../../data/public';
|
||||
import { getDetails } from './lib/get_details';
|
||||
import { getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter';
|
||||
import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list';
|
||||
import { groupFields } from "./lib/group_fields";
|
||||
import {
|
||||
IndexPatternField,
|
||||
IndexPattern,
|
||||
UI_SETTINGS,
|
||||
} from "../../../../../data/public";
|
||||
import { getDetails } from "./lib/get_details";
|
||||
import { getDefaultFieldFilter, setFieldFilterProp } from "./lib/field_filter";
|
||||
import { getIndexPatternFieldList } from "./lib/get_index_pattern_field_list";
|
||||
// import { getServices } from '../../../kibana_services';
|
||||
import { Tree, Icon } from "antd";
|
||||
|
||||
export interface DiscoverSidebarProps {
|
||||
/**
|
||||
|
@ -57,7 +62,11 @@ export interface DiscoverSidebarProps {
|
|||
/**
|
||||
* Callback function when adding a filter from sidebar
|
||||
*/
|
||||
onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
|
||||
onAddFilter: (
|
||||
field: IndexPatternField | string,
|
||||
value: string,
|
||||
type: "+" | "-"
|
||||
) => void;
|
||||
/**
|
||||
* Callback function when removing a field
|
||||
* @param fieldName
|
||||
|
@ -72,6 +81,7 @@ export interface DiscoverSidebarProps {
|
|||
*/
|
||||
setIndexPattern: (id: string) => void;
|
||||
isClosed: boolean;
|
||||
indices: string[];
|
||||
}
|
||||
|
||||
export function DiscoverSidebar({
|
||||
|
@ -85,16 +95,22 @@ export function DiscoverSidebar({
|
|||
selectedIndexPattern,
|
||||
setIndexPattern,
|
||||
isClosed,
|
||||
indices,
|
||||
}: DiscoverSidebarProps) {
|
||||
const [showFields, setShowFields] = useState(false);
|
||||
const [fields, setFields] = useState<IndexPatternField[] | null>(null);
|
||||
const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter());
|
||||
const [fieldFilterState, setFieldFilterState] = useState(
|
||||
getDefaultFieldFilter()
|
||||
);
|
||||
// const services = useMemo(() => getServices(), []);
|
||||
|
||||
useEffect(() => {
|
||||
const newFields = getIndexPatternFieldList(selectedIndexPattern, fieldCounts);
|
||||
const newFields = getIndexPatternFieldList(
|
||||
selectedIndexPattern,
|
||||
fieldCounts
|
||||
);
|
||||
setFields(newFields);
|
||||
}, [selectedIndexPattern, fieldCounts, hits, ]);//services
|
||||
}, [selectedIndexPattern, fieldCounts, hits]); //services
|
||||
|
||||
const onChangeFieldSearch = useCallback(
|
||||
(field: string, value: string | boolean | undefined) => {
|
||||
|
@ -105,27 +121,51 @@ export function DiscoverSidebar({
|
|||
);
|
||||
|
||||
const getDetailsByField = useCallback(
|
||||
(ipField: IndexPatternField) => getDetails(ipField, hits, columns, selectedIndexPattern),
|
||||
(ipField: IndexPatternField) =>
|
||||
getDetails(ipField, hits, columns, selectedIndexPattern),
|
||||
[hits, columns, selectedIndexPattern]
|
||||
);
|
||||
|
||||
const popularLimit = 5;//services.uiSettings.get(FIELDS_LIMIT_SETTING);
|
||||
const useShortDots = false;//services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE);
|
||||
const popularLimit = 5; //services.uiSettings.get(FIELDS_LIMIT_SETTING);
|
||||
const useShortDots = false; //services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE);
|
||||
|
||||
const {
|
||||
selected: selectedFields,
|
||||
popular: popularFields,
|
||||
unpopular: unpopularFields,
|
||||
} = useMemo(() => groupFields(fields, columns, popularLimit, fieldCounts, fieldFilterState), [
|
||||
fields,
|
||||
columns,
|
||||
popularLimit,
|
||||
fieldCounts,
|
||||
fieldFilterState,
|
||||
]);
|
||||
fieldsTree,
|
||||
} = useMemo(() => {
|
||||
const groupedFields = groupFields(
|
||||
fields,
|
||||
columns,
|
||||
popularLimit,
|
||||
fieldCounts,
|
||||
fieldFilterState
|
||||
);
|
||||
const fieldsTree = {};
|
||||
groupedFields.unpopular.forEach((field) => {
|
||||
const keys = field.displayName.split(".");
|
||||
let currentObj = fieldsTree;
|
||||
keys.forEach((key: string, i: number) => {
|
||||
if (!currentObj[key]) {
|
||||
currentObj[key] = {};
|
||||
}
|
||||
if (keys.length == i + 1) {
|
||||
field.isLeaf = true;
|
||||
currentObj[key] = field;
|
||||
return;
|
||||
}
|
||||
currentObj = currentObj[key];
|
||||
});
|
||||
});
|
||||
return {
|
||||
...groupedFields,
|
||||
fieldsTree,
|
||||
};
|
||||
}, [fields, columns, popularLimit, fieldCounts, fieldFilterState]);
|
||||
|
||||
const fieldTypes = useMemo(() => {
|
||||
const result = ['any'];
|
||||
const result = ["any"];
|
||||
if (Array.isArray(fields)) {
|
||||
for (const field of fields) {
|
||||
if (result.indexOf(field.type) === -1) {
|
||||
|
@ -136,20 +176,56 @@ export function DiscoverSidebar({
|
|||
return result;
|
||||
}, [fields]);
|
||||
|
||||
if (!selectedIndexPattern || !fields || isClosed ) {
|
||||
if (!selectedIndexPattern || !fields || isClosed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const buildTree = (treeObj: any) => {
|
||||
return Object.keys(treeObj).map((key) => {
|
||||
if (treeObj[key].isLeaf) {
|
||||
return (
|
||||
<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 (
|
||||
<EuiHideFor sizes={['xs', 's']}>
|
||||
<section
|
||||
className="sidebar-list"
|
||||
aria-label={'Index and fields'}
|
||||
>
|
||||
<EuiHideFor sizes={["xs", "s"]}>
|
||||
<section className="sidebar-list" aria-label={"Index and fields"}>
|
||||
<DiscoverIndexPattern
|
||||
selectedIndexPattern={selectedIndexPattern}
|
||||
setIndexPattern={setIndexPattern}
|
||||
indexPatternList={sortBy(indexPatternList, (o) => o.attributes.viewName)}
|
||||
indexPatternList={sortBy(
|
||||
indexPatternList,
|
||||
(o) => o.attributes.viewName
|
||||
)}
|
||||
indices={indices}
|
||||
/>
|
||||
<div className="dscSidebar__item">
|
||||
<form>
|
||||
|
@ -164,9 +240,7 @@ export function DiscoverSidebar({
|
|||
{fields.length > 0 && (
|
||||
<>
|
||||
<EuiTitle size="xxxs" id="selected_fields">
|
||||
<h3>
|
||||
Selected fields
|
||||
</h3>
|
||||
<h3>Selected fields</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="xs" />
|
||||
<ul
|
||||
|
@ -196,10 +270,12 @@ export function DiscoverSidebar({
|
|||
})}
|
||||
</ul>
|
||||
<div className="euiFlexGroup euiFlexGroup--gutterMedium">
|
||||
<EuiTitle size="xxxs" id="available_fields" className="euiFlexItem">
|
||||
<h3>
|
||||
Available fields
|
||||
</h3>
|
||||
<EuiTitle
|
||||
size="xxxs"
|
||||
id="available_fields"
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<h3>Available fields</h3>
|
||||
</EuiTitle>
|
||||
{/* <div className="euiFlexItem euiFlexItem--flexGrowZero">
|
||||
<EuiButtonIcon
|
||||
|
@ -220,15 +296,20 @@ export function DiscoverSidebar({
|
|||
<div>
|
||||
<EuiTitle
|
||||
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
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<ul
|
||||
className={`dscFieldList dscFieldList--popular ${
|
||||
!showFields ? 'hidden-sm hidden-xs' : ''
|
||||
!showFields ? "hidden-sm hidden-xs" : ""
|
||||
}`}
|
||||
aria-labelledby="available_fields available_fields_popular"
|
||||
data-test-subj={`fieldList-popular`}
|
||||
|
@ -256,9 +337,9 @@ export function DiscoverSidebar({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<ul
|
||||
{/* <ul
|
||||
className={`dscFieldList dscFieldList--unpopular ${
|
||||
!showFields ? 'hidden-sm hidden-xs' : ''
|
||||
!showFields ? "hidden-sm hidden-xs" : ""
|
||||
}`}
|
||||
aria-labelledby="available_fields"
|
||||
data-test-subj={`fieldList-unpopular`}
|
||||
|
@ -282,9 +363,18 @@ export function DiscoverSidebar({
|
|||
</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>
|
||||
</section>
|
||||
</EuiHideFor>
|
||||
</EuiHideFor>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -24,8 +24,8 @@ import {
|
|||
EuiText,
|
||||
EuiSelect,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
} from "@elastic/eui";
|
||||
import moment from "moment";
|
||||
|
||||
export interface TimechartHeaderProps {
|
||||
/**
|
||||
|
@ -39,6 +39,7 @@ export interface TimechartHeaderProps {
|
|||
scaled?: boolean;
|
||||
description?: string;
|
||||
scale?: number;
|
||||
timeFieldName: string;
|
||||
};
|
||||
/**
|
||||
* Range of dates to be displayed
|
||||
|
@ -59,6 +60,7 @@ export interface TimechartHeaderProps {
|
|||
* selected interval
|
||||
*/
|
||||
stateInterval: string;
|
||||
hits: number;
|
||||
}
|
||||
|
||||
export function TimechartHeader({
|
||||
|
@ -68,12 +70,13 @@ export function TimechartHeader({
|
|||
options,
|
||||
onChangeInterval,
|
||||
stateInterval,
|
||||
hits,
|
||||
}: TimechartHeaderProps) {
|
||||
const [interval, setInterval] = useState(stateInterval);
|
||||
const toMoment = useCallback(
|
||||
(datetime: string) => {
|
||||
if (!datetime) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
if (!dateFormat) {
|
||||
return datetime;
|
||||
|
@ -92,27 +95,36 @@ export function TimechartHeader({
|
|||
onChangeInterval(e.target.value);
|
||||
};
|
||||
|
||||
if (!timeRange || !bucketInterval) {
|
||||
return null;
|
||||
}
|
||||
// if (!timeRange || !bucketInterval) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" responsive justifyContent="center" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={ 'To change the time, use the global time filter above'}
|
||||
delay="long"
|
||||
>
|
||||
<EuiText data-test-subj="discoverIntervalDateRange" size="s">
|
||||
{`${toMoment(timeRange.from)} - ${toMoment(timeRange.to)} ${
|
||||
interval !== 'auto'
|
||||
? 'per'
|
||||
: ''
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
responsive
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
Found <span style={{ fontSize: 16, fontWeight: "bold" }}>{hits}</span>{" "}
|
||||
records
|
||||
{timeRange && (
|
||||
<span style={{ fontSize: 12, marginLeft: 5 }}>
|
||||
{`between ${toMoment(timeRange.from)} and ${toMoment(
|
||||
timeRange.to
|
||||
)} ${
|
||||
interval !== "auto" ? "" : "" //per
|
||||
}`}
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
</span>
|
||||
)}
|
||||
{bucketInterval && (
|
||||
<span>{`(${bucketInterval.timeFieldName} per ${bucketInterval.description})`}</span>
|
||||
)}
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
{/* <EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
aria-label={'Time interval'}
|
||||
compressed
|
||||
|
@ -145,7 +157,7 @@ export function TimechartHeader({
|
|||
) : undefined
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem> */}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,4 +23,5 @@ const DOT_PREFIX_RE = /(.).+?\./g;
|
|||
* Convert a dot.notated.string into a short
|
||||
* version (d.n.string)
|
||||
*/
|
||||
export const shortenDottedString = (input: string) => input.replace(DOT_PREFIX_RE, '$1.');
|
||||
export const shortenDottedString = (input: string) =>
|
||||
input.replace(DOT_PREFIX_RE, ""); //'$1.');
|
||||
|
|
|
@ -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',
|
||||
payload: {
|
||||
clusterList: newClusterList,
|
||||
clusterTotal: res.total,
|
||||
search: {
|
||||
...search,
|
||||
cluster: payload,
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import * as React from 'react';
|
||||
import {Tabs} from 'antd';
|
||||
import {Tabs, Row, Col, Card} from 'antd';
|
||||
import Clusters from './components/clusters';
|
||||
import styles from "./Overview.less";
|
||||
import {connect} from "dva";
|
||||
import {formatter} from '@/lib/format';
|
||||
const {TabPane} = Tabs;
|
||||
|
||||
|
||||
|
||||
const panes = [
|
||||
{ title: 'Clusters', component: Clusters, key: 'clusters' },
|
||||
{ title: 'Hosts', component: 'Content of Tab 2', key: 'hosts' },
|
||||
|
@ -11,9 +15,66 @@ const panes = [
|
|||
{title: 'Indices', component: 'Content of Tab 3',key: 'indices'},
|
||||
];
|
||||
|
||||
const NewOverview = ()=>{
|
||||
const NewOverview = (props)=>{
|
||||
React.useEffect(()=>{
|
||||
const {dispatch} = props;
|
||||
dispatch({
|
||||
type: 'clusterOverview/fetchOverview',
|
||||
})
|
||||
},[])
|
||||
const {clusterTotal, overview} = props;
|
||||
|
||||
const totalStoreSize = formatter.bytes(overview?.total_store_size_in_bytes || 0);
|
||||
|
||||
return (<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>
|
||||
<Tabs
|
||||
onChange={()=>{}}
|
||||
|
@ -31,4 +92,10 @@ const NewOverview = ()=>{
|
|||
</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: '索引名称',
|
||||
dataIndex: 'index',
|
||||
render: (text, record) => (
|
||||
<a onClick={()=>{
|
||||
this.setState({
|
||||
editingIndex: record,
|
||||
drawerVisible: true,
|
||||
});
|
||||
}}>{text}</a>
|
||||
<Link to={`/data/discover?viewID=${text}`}>{text}</Link>
|
||||
)
|
||||
},
|
||||
{
|
||||
|
@ -162,8 +157,13 @@ class Index extends PureComponent {
|
|||
title: '操作',
|
||||
render: (text, record) => (
|
||||
<Fragment>
|
||||
{/* <a onClick={() => this.handleUpdateModalVisible(true, record)}>设置</a>
|
||||
<Divider type="vertical" /> */}
|
||||
<a onClick={() => {
|
||||
this.setState({
|
||||
editingIndex: record,
|
||||
drawerVisible: true,
|
||||
});
|
||||
}}>设置</a>
|
||||
<Divider type="vertical" />
|
||||
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDeleteClick(record.index)}>
|
||||
<a>删除</a>
|
||||
</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 {
|
||||
padding: 0 5 5 0;
|
||||
overflow: hidden; // Ensures horizontal scroll of table
|
||||
box-shadow: 0 2px 2px -1px rgb(152 162 179 / 30%), 0 1px 5px -2px rgb(152 162 179 / 30%);
|
||||
box-shadow: 0 2px 2px -1px rgb(152 162 179 / 30%),
|
||||
0 1px 5px -2px rgb(152 162 179 / 30%);
|
||||
// border: 1px solid #D3DAE6;
|
||||
border-radius: 4px;
|
||||
flex-grow: 1;
|
||||
border-radius: 4px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dscPageContent {
|
||||
//border: $euiBorderThin;
|
||||
// text-align: center;
|
||||
box-shadow: none !important;
|
||||
.euiDataGrid--fullScreen{
|
||||
.euiDataGrid--fullScreen {
|
||||
z-index: 801;
|
||||
}
|
||||
position: relative;
|
||||
|
@ -57,7 +58,7 @@
|
|||
|
||||
// SASSTODO: the visualizing component should have an option or a modifier
|
||||
.series > rect {
|
||||
fill-opacity: .5;
|
||||
fill-opacity: 0.5;
|
||||
stroke-width: 1;
|
||||
}
|
||||
}
|
||||
|
@ -75,23 +76,24 @@
|
|||
// padding: 5 5 0 5;
|
||||
// }
|
||||
.euiDataGrid--headerUnderline .euiDataGridHeaderCell {
|
||||
border-bottom: 1px solid #D3DAE6 !important;
|
||||
border-bottom: 1px solid #d3dae6 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
.dscSidebar{
|
||||
.euiButton--text, .euiButton--text:hover{
|
||||
.dscSidebar {
|
||||
.euiButton--text,
|
||||
.euiButton--text:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-list{
|
||||
.sidebar-list {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.euiSuperUpdateButton.euiButton--success {
|
||||
background-color: #017D73 !important;
|
||||
border-color: #017D73 !important;
|
||||
color: #FFF !important;
|
||||
background-color: #017d73 !important;
|
||||
border-color: #017d73 !important;
|
||||
color: #fff !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
|
@ -109,6 +111,64 @@
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
.kbnQueryBar__wrap{
|
||||
.kbnQueryBar__wrap {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {connect} from 'dva';
|
||||
import {Button, Icon, Menu, Dropdown, } from 'antd';
|
||||
import Tabs from '@/components/infini/tabs';
|
||||
import {DraggableTabs} from '@/components/infini/tabs/DraggableTabs';
|
||||
import {useState, useReducer, useCallback, useEffect, useMemo, useRef, useLayoutEffect} from 'react';
|
||||
import {useLocalStorage} from '@/lib/hooks/storage';
|
||||
import {setClusterID} from '../../components/kibana/console/modules/mappings/mappings';
|
||||
import {TabTitle} from './console_tab_title';
|
||||
import '@/assets/utility.scss';
|
||||
import Console from "../../components/kibana/console/components/Console";
|
||||
import { connect } from "dva";
|
||||
import { Button, Icon, Menu, Dropdown } from "antd";
|
||||
import Tabs from "@/components/infini/tabs";
|
||||
import { DraggableTabs } from "@/components/infini/tabs/DraggableTabs";
|
||||
import {
|
||||
useState,
|
||||
useReducer,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useLayoutEffect,
|
||||
} from "react";
|
||||
import { useLocalStorage } from "@/lib/hooks/storage";
|
||||
import { setClusterID } from "../../components/kibana/console/modules/mappings/mappings";
|
||||
import { TabTitle } from "./console_tab_title";
|
||||
import "@/assets/utility.scss";
|
||||
import { Resizable } from "re-resizable";
|
||||
import {ResizeBar} from '@/components/infini/resize_bar';
|
||||
import NewTabMenu from './NewTabMenu';
|
||||
import { ResizeBar } from "@/components/infini/resize_bar";
|
||||
import NewTabMenu from "./NewTabMenu";
|
||||
|
||||
import maximizeSvg from "@/assets/window-maximize.svg";
|
||||
import restoreSvg from "@/assets/window-restore.svg";
|
||||
|
||||
const MaximizeIcon = (props = {}) => {
|
||||
return <img height="14px" width="14px" {...props} src={maximizeSvg} />;
|
||||
};
|
||||
const RestoreIcon = (props = {}) => {
|
||||
return <img height="14px" width="14px" {...props} src={restoreSvg} />;
|
||||
};
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const TabConsole = (props:any)=>{
|
||||
return (
|
||||
<Console {...props}/>
|
||||
)
|
||||
}
|
||||
|
||||
const TabConsole = (props: any) => {
|
||||
return <Console {...props} />;
|
||||
};
|
||||
|
||||
// export default connect(({
|
||||
// global
|
||||
|
@ -29,331 +44,381 @@ const TabConsole = (props:any)=>{
|
|||
|
||||
const addTab = (state: any, action: any) => {
|
||||
const { panes } = state;
|
||||
const {cluster} = action.payload;
|
||||
const { cluster } = action.payload;
|
||||
const activeKey = `${cluster.id}:${new Date().valueOf()}`;
|
||||
panes.push({ key: activeKey, cluster_id: cluster.id, title: cluster.name});
|
||||
panes.push({ key: activeKey, cluster_id: cluster.id, title: cluster.name });
|
||||
return {
|
||||
...state,
|
||||
panes,
|
||||
activeKey,
|
||||
}
|
||||
}
|
||||
const removeTab = (state: any, action: any) =>{
|
||||
};
|
||||
};
|
||||
const removeTab = (state: any, action: any) => {
|
||||
const { activeKey, panes } = state;
|
||||
const {targetKey} = action.payload;
|
||||
const newPanes = panes.filter(pane => pane.key !== targetKey);
|
||||
const { targetKey } = action.payload;
|
||||
|
||||
const newPanes = panes.filter((pane) => pane.key !== targetKey);
|
||||
return {
|
||||
...state,
|
||||
panes: newPanes,
|
||||
activeKey: panes[0]?.key,
|
||||
}
|
||||
}
|
||||
activeKey: activeKey == targetKey ? newPanes[0]?.key : activeKey,
|
||||
};
|
||||
};
|
||||
|
||||
const consoleTabReducer = (state: any, action: any) => {
|
||||
const {type, payload} = action;
|
||||
const { type, payload } = action;
|
||||
let newState = state;
|
||||
switch(type){
|
||||
case 'add':
|
||||
switch (type) {
|
||||
case "add":
|
||||
newState = addTab(state, action);
|
||||
break;
|
||||
case 'remove':
|
||||
newState = removeTab(state, action);
|
||||
case "remove":
|
||||
newState = removeTab(state, action);
|
||||
break;
|
||||
case 'change':
|
||||
case "change":
|
||||
newState = {
|
||||
...state,
|
||||
activeKey: payload.activeKey,
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'saveTitle':
|
||||
const {key, title} = action.payload;
|
||||
const newPanes = state.panes.map((pane: any)=>{
|
||||
if(pane.key == key){
|
||||
case "saveTitle":
|
||||
const { key, title } = action.payload;
|
||||
const newPanes = state.panes.map((pane: any) => {
|
||||
if (pane.key == key) {
|
||||
return {
|
||||
...pane,
|
||||
title,
|
||||
}
|
||||
};
|
||||
}
|
||||
return pane;
|
||||
});
|
||||
newState = {
|
||||
...state,
|
||||
panes: newPanes,
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'saveContent':
|
||||
const panes = state.panes.map((pane)=>{
|
||||
if(pane.key == state.activeKey){
|
||||
case "saveContent":
|
||||
const panes = state.panes.map((pane) => {
|
||||
if (pane.key == state.activeKey) {
|
||||
return {
|
||||
...pane,
|
||||
content: action.payload.content,
|
||||
}
|
||||
};
|
||||
}
|
||||
return pane;
|
||||
});
|
||||
newState = ({
|
||||
newState = {
|
||||
...state,
|
||||
panes,
|
||||
});
|
||||
};
|
||||
break;
|
||||
case 'saveOrder':
|
||||
newState = ({
|
||||
case "saveOrder":
|
||||
newState = {
|
||||
...state,
|
||||
order: action.payload.order,
|
||||
});
|
||||
};
|
||||
default:
|
||||
}
|
||||
// setLocalState(newState);
|
||||
return newState;
|
||||
}
|
||||
};
|
||||
|
||||
function calcHeightToPX(height: string){
|
||||
const intHeight = parseInt(height)
|
||||
if(height.endsWith('vh')){
|
||||
return Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) * intHeight / 100;
|
||||
}else{
|
||||
function calcHeightToPX(height: string) {
|
||||
const intHeight = parseInt(height);
|
||||
if (height.endsWith("vh")) {
|
||||
return (
|
||||
(Math.max(
|
||||
document.documentElement.clientHeight || 0,
|
||||
window.innerHeight || 0
|
||||
) *
|
||||
intHeight) /
|
||||
100
|
||||
);
|
||||
} else {
|
||||
return intHeight;
|
||||
}
|
||||
}
|
||||
|
||||
export const ConsoleUI = ({selectedCluster,
|
||||
clusterList,
|
||||
clusterStatus,
|
||||
minimize=false,
|
||||
export const ConsoleUI = ({
|
||||
selectedCluster,
|
||||
clusterList,
|
||||
clusterStatus,
|
||||
minimize = false,
|
||||
onMinimizeClick,
|
||||
resizeable=false,
|
||||
height='50vh'
|
||||
}: any)=>{
|
||||
const clusterMap = useMemo(()=>{
|
||||
resizeable = false,
|
||||
height = "50vh",
|
||||
}: any) => {
|
||||
const clusterMap = useMemo(() => {
|
||||
let cm = {};
|
||||
if(!clusterStatus){
|
||||
if (!clusterStatus) {
|
||||
return cm;
|
||||
}
|
||||
(clusterList || []).map((cluster: any)=>{
|
||||
(clusterList || []).map((cluster: any) => {
|
||||
cluster.status = clusterStatus[cluster.id]?.health?.status;
|
||||
if(!clusterStatus[cluster.id]?.available){
|
||||
cluster.status = 'unavailable';
|
||||
}
|
||||
if (!clusterStatus[cluster.id]?.available) {
|
||||
cluster.status = "unavailable";
|
||||
}
|
||||
cm[cluster.id] = cluster;
|
||||
});
|
||||
return cm;
|
||||
}, [clusterList, clusterStatus])
|
||||
const initialDefaultState = ()=>{
|
||||
}, [clusterList, clusterStatus]);
|
||||
const initialDefaultState = () => {
|
||||
let defaultCluster = selectedCluster;
|
||||
if(!defaultCluster.id){
|
||||
defaultCluster = clusterList[0] ;
|
||||
if (!defaultCluster.id) {
|
||||
defaultCluster = clusterList[0];
|
||||
}
|
||||
const defaultActiveKey = `${defaultCluster.id || ''}:${new Date().valueOf()}`;
|
||||
const defaultState = defaultCluster? {
|
||||
panes:[{
|
||||
key: defaultActiveKey, cluster_id: defaultCluster.id, title: defaultCluster.name
|
||||
}],
|
||||
activeKey: defaultActiveKey,
|
||||
}: {panes:[],activeKey:''};
|
||||
return defaultState
|
||||
}
|
||||
|
||||
const [localState, setLocalState, removeLocalState] = useLocalStorage("console:state", initialDefaultState, {
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse,
|
||||
})
|
||||
const [tabState, dispatch] = useReducer(consoleTabReducer, localState)
|
||||
|
||||
useEffect(()=>{
|
||||
if(tabState.panes.length == 0){
|
||||
removeLocalState()
|
||||
return
|
||||
}
|
||||
setLocalState(tabState)
|
||||
}, [tabState])
|
||||
|
||||
const saveEditorContent = useCallback((content)=>{
|
||||
dispatch({
|
||||
type: 'saveContent',
|
||||
payload: {
|
||||
content
|
||||
}
|
||||
})
|
||||
}, [dispatch])
|
||||
|
||||
const onChange = (activeKey:string) => {
|
||||
dispatch({
|
||||
type: 'change',
|
||||
payload: {
|
||||
activeKey
|
||||
}
|
||||
})
|
||||
const defaultActiveKey = `${defaultCluster.id ||
|
||||
""}:${new Date().valueOf()}`;
|
||||
const defaultState = defaultCluster
|
||||
? {
|
||||
panes: [
|
||||
{
|
||||
key: defaultActiveKey,
|
||||
cluster_id: defaultCluster.id,
|
||||
title: defaultCluster.name,
|
||||
},
|
||||
],
|
||||
activeKey: defaultActiveKey,
|
||||
}
|
||||
: { panes: [], activeKey: "" };
|
||||
return defaultState;
|
||||
};
|
||||
|
||||
const onEdit = (targetKey: string | React.MouseEvent<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({
|
||||
type: action,
|
||||
payload: {
|
||||
targetKey,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const newTabClick = useCallback((param: any)=>{
|
||||
const cluster = clusterList.find(item=>item.id == param.id);
|
||||
if(!cluster){
|
||||
console.log('cluster not found')
|
||||
return;
|
||||
}
|
||||
dispatch({
|
||||
type:'add',
|
||||
payload: {
|
||||
cluster,
|
||||
const newTabClick = useCallback(
|
||||
(param: any) => {
|
||||
const cluster = clusterList.find((item) => item.id == param.id);
|
||||
if (!cluster) {
|
||||
console.log("cluster not found");
|
||||
return;
|
||||
}
|
||||
})
|
||||
},[clusterList])
|
||||
|
||||
const menu = (
|
||||
// <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"/>
|
||||
dispatch({
|
||||
type: "add",
|
||||
payload: {
|
||||
cluster,
|
||||
},
|
||||
});
|
||||
},
|
||||
[clusterList]
|
||||
);
|
||||
|
||||
|
||||
// const menu = (
|
||||
// // <Menu onClick={newTabClick}>
|
||||
// // {(clusterList||[]).map((cluster:any)=>{
|
||||
// // return <Menu.Item key={cluster.id}>{cluster.name}</Menu.Item>
|
||||
// // })}
|
||||
// // </Menu>
|
||||
// <NewTabMenu data={clusterList}
|
||||
// onItemClick={newTabClick}
|
||||
// clusterStatus={clusterStatus}
|
||||
// size={10}
|
||||
// width="300px"/>
|
||||
// );
|
||||
|
||||
const rootRef = useRef(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const fullscreenClick = ()=>{
|
||||
if(rootRef.current != null){
|
||||
if(!isFullscreen){
|
||||
const fullscreenClick = () => {
|
||||
if (rootRef.current != null) {
|
||||
if (!isFullscreen) {
|
||||
rootRef.current.className = rootRef.current.className + " fullscreen";
|
||||
// rootRef.current.style.overflow = 'scroll';
|
||||
}else{
|
||||
rootRef.current.className = rootRef.current.className.replace(' fullscreen', '');
|
||||
} else {
|
||||
rootRef.current.className = rootRef.current.className.replace(
|
||||
" fullscreen",
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
setEditorHeight(rootRef.current.clientHeight)
|
||||
setIsFullscreen(!isFullscreen)
|
||||
}
|
||||
setEditorHeight(rootRef.current.clientHeight);
|
||||
setIsFullscreen(!isFullscreen);
|
||||
};
|
||||
|
||||
const tabBarExtra ={
|
||||
left: <div>
|
||||
{minimize? <Button size="small" onClick={onMinimizeClick} style={{marginLeft:5}} title="Close">
|
||||
{/* <Icon type="minus"/> */}
|
||||
<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>
|
||||
}
|
||||
const tabBarExtra = {
|
||||
left: (
|
||||
<div className="tabbar-icon" onClick={onMinimizeClick}>
|
||||
{minimize ? <Icon type="code" /> : null}
|
||||
</div>
|
||||
),
|
||||
append:(
|
||||
<div>
|
||||
<Dropdown overlay={menu} placement="bottomLeft">
|
||||
<Button size="small" style={{marginRight:5}}>
|
||||
<Icon type="plus"/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
),
|
||||
right: (
|
||||
<>
|
||||
<div className="tabbar-icon" onClick={onMinimizeClick}>
|
||||
{minimize ? <Icon type="minus" /> : null}
|
||||
</div>
|
||||
<div className="tabbar-icon" onClick={fullscreenClick}>
|
||||
{isFullscreen ? <RestoreIcon /> : <MaximizeIcon />}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
append: (
|
||||
<NewTabMenu
|
||||
data={clusterList}
|
||||
onItemClick={newTabClick}
|
||||
clusterStatus={clusterStatus}
|
||||
size={10}
|
||||
width="300px"
|
||||
>
|
||||
<div className="tabbar-icon">
|
||||
<Icon type="plus" />
|
||||
</div>
|
||||
</NewTabMenu>
|
||||
),
|
||||
};
|
||||
|
||||
setClusterID(tabState.activeKey?.split(':')[0]);
|
||||
const panes = tabState.panes.filter((pane: any)=>{
|
||||
return typeof clusterMap[pane.cluster_id] != 'undefined';
|
||||
})
|
||||
setClusterID(tabState.activeKey?.split(":")[0]);
|
||||
const panes = tabState.panes.filter((pane: any) => {
|
||||
return typeof clusterMap[pane.cluster_id] != "undefined";
|
||||
});
|
||||
|
||||
const saveTitle = (key: string, title: string)=>{
|
||||
const saveTitle = (key: string, title: string) => {
|
||||
dispatch({
|
||||
type:'saveTitle',
|
||||
type: "saveTitle",
|
||||
payload: {
|
||||
key,
|
||||
title,
|
||||
}
|
||||
})
|
||||
}
|
||||
const [editorHeight, setEditorHeight] = useState(calcHeightToPX(height))
|
||||
const onResize = (_env, _dir, refToElement, delta)=>{
|
||||
},
|
||||
});
|
||||
};
|
||||
const [editorHeight, setEditorHeight] = useState(calcHeightToPX(height));
|
||||
const onResize = (_env, _dir, refToElement, delta) => {
|
||||
// console.log(refToElement.offsetHeight, delta)
|
||||
setEditorHeight(refToElement.clientHeight)
|
||||
}
|
||||
setEditorHeight(refToElement.clientHeight);
|
||||
};
|
||||
|
||||
const disableWindowScroll = ()=>{
|
||||
document.body.style.overflow = 'hidden'
|
||||
}
|
||||
const disableWindowScroll = () => {
|
||||
document.body.style.overflow = "hidden";
|
||||
};
|
||||
|
||||
const enableWindowScroll = ()=>{
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
const onTabNodeMoved=(newOrder:string[])=>{
|
||||
const enableWindowScroll = () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
const onTabNodeMoved = (newOrder: string[]) => {
|
||||
dispatch({
|
||||
type:'saveOrder',
|
||||
type: "saveOrder",
|
||||
payload: {
|
||||
order: newOrder,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
order: newOrder,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
defaultSize={{
|
||||
height: editorHeight||'50vh'
|
||||
}}
|
||||
minHeight={70}
|
||||
maxHeight="100vh"
|
||||
handleComponent={{ top: <ResizeBar/> }}
|
||||
onResize={onResize}
|
||||
enable={{
|
||||
top: resizeable,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}>
|
||||
<div style={{background:'#fff', height:'100%'}}
|
||||
onMouseOver={disableWindowScroll}
|
||||
onMouseOut={enableWindowScroll}
|
||||
id="console"
|
||||
ref={rootRef} >
|
||||
<DraggableTabs
|
||||
onChange={onChange}
|
||||
activeKey={tabState.activeKey}
|
||||
type="editable-card"
|
||||
onEdit={onEdit}
|
||||
hideAdd
|
||||
initialOrder={tabState.order}
|
||||
tabBarExtraContent={tabBarExtra}
|
||||
onTabNodeMoved={onTabNodeMoved}
|
||||
defaultSize={{
|
||||
height: editorHeight || "50vh",
|
||||
}}
|
||||
minHeight={70}
|
||||
maxHeight="100vh"
|
||||
handleComponent={{ top: <ResizeBar /> }}
|
||||
onResize={onResize}
|
||||
enable={{
|
||||
top: resizeable,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ background: "#fff", height: "100%" }}
|
||||
onMouseOver={disableWindowScroll}
|
||||
onMouseOut={enableWindowScroll}
|
||||
id="console"
|
||||
ref={rootRef}
|
||||
>
|
||||
{panes.map(pane => (
|
||||
<TabPane tab={<TabTitle title={pane.title} onTitleChange={(title)=>{saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}>
|
||||
<TabConsole height={editorHeight - 40} selectedCluster={clusterMap[pane.cluster_id]} paneKey={pane.key} saveEditorContent={saveEditorContent} initialText={pane.content} />
|
||||
{/* {pane.content} */}
|
||||
</TabPane>
|
||||
))}
|
||||
</DraggableTabs>
|
||||
</div>
|
||||
</Resizable>
|
||||
<DraggableTabs
|
||||
onChange={onChange}
|
||||
activeKey={tabState.activeKey}
|
||||
type="editable-card"
|
||||
onEdit={onEdit}
|
||||
hideAdd
|
||||
initialOrder={tabState.order}
|
||||
tabBarExtraContent={tabBarExtra}
|
||||
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(({
|
||||
global
|
||||
})=>({
|
||||
export default connect(({ global }) => ({
|
||||
selectedCluster: global.selectedCluster,
|
||||
clusterList: global.clusterList,
|
||||
clusterStatus: global.clusterStatus,
|
||||
height: window.innerHeight - 75 + 'px',
|
||||
}))(ConsoleUI);
|
||||
height: window.innerHeight - 75 + "px",
|
||||
}))(ConsoleUI);
|
||||
|
|
|
@ -22,11 +22,16 @@ class NewTabMenu extends React.Component{
|
|||
initialLoad: true,
|
||||
dataSource: [...props.data],
|
||||
dataSourceKey: 1,
|
||||
selectedIndex: -1,
|
||||
overlayVisible: false,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
handleInfiniteOnLoad = (current) => {
|
||||
let {size } = this.props;
|
||||
let targetLength = current * size;
|
||||
|
@ -54,17 +59,56 @@ class NewTabMenu extends React.Component{
|
|||
dataSource: newData,
|
||||
data: newData,
|
||||
hasMore: newData.length > this.props.size,
|
||||
selectedIndex: -1,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
selectOffset = (offset)=> {
|
||||
let {selectedIndex, data} = this.state;
|
||||
const len = data.length;
|
||||
selectedIndex = (selectedIndex + offset + len) % len;
|
||||
// const item = data[selectedIndex];
|
||||
this.setState({
|
||||
selectedIndex,
|
||||
})
|
||||
}
|
||||
|
||||
onKeyDown = (e) => {
|
||||
const { which } = e;
|
||||
// e.preventDefault();
|
||||
|
||||
switch (which) {
|
||||
case 38:
|
||||
this.selectOffset(-1);
|
||||
e.preventDefault();
|
||||
e.stopPropagation()
|
||||
break;
|
||||
case 40:
|
||||
this.selectOffset(1);
|
||||
e.stopPropagation()
|
||||
break;
|
||||
case 13:
|
||||
const {data, selectedIndex} = this.state;
|
||||
if(selectedIndex > -1){
|
||||
this.handleItemClick(data[selectedIndex]);
|
||||
this.setState({ overlayVisible: false })
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render(){
|
||||
const {clusterStatus} = this.props;
|
||||
return (<div className={styles.dropmenu} style={{width: this.props.width}}>
|
||||
<div className={styles.infiniteContainer} style={{height: this.props.height}}>
|
||||
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}>
|
||||
<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}}>
|
||||
<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>
|
||||
<InfiniteScroll
|
||||
initialLoad={this.state.initialLoad}
|
||||
|
@ -74,11 +118,12 @@ class NewTabMenu extends React.Component{
|
|||
>
|
||||
<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 || []).map((item)=>{
|
||||
{(this.state.data || []).map((item, idx)=>{
|
||||
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
|
||||
return <DropdownItem key={item.id}
|
||||
clusterItem={item}
|
||||
clusterStatus={cstatus}
|
||||
isSelected={this.state.selectedIndex == idx}
|
||||
onClick={() => {
|
||||
this.handleItemClick(item)
|
||||
}}
|
||||
|
@ -93,6 +138,17 @@ class NewTabMenu extends React.Component{
|
|||
</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{
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
overflow: hidden;
|
||||
.input-eidtor{
|
||||
border-radius: 0;
|
||||
border:none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
// border-bottom: 1px solid #ccc;
|
||||
border-right: none;
|
||||
padding-left: 1em;
|
||||
color:rgba(0, 0, 0, 0.65);
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
background-color: #939ea0;
|
||||
color: #fff;
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.icon-cont{
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
#console{
|
||||
|
|
|
@ -26,16 +26,26 @@ export const TabTitle = ({title, onTitleChange}: TabTitleProps)=>{
|
|||
}
|
||||
},[editable])
|
||||
const inputRef = useRef(null);
|
||||
const onKeyDown = (e: any)=>{
|
||||
const { which } = e;
|
||||
|
||||
switch (which) {
|
||||
case 13:
|
||||
e.target.blur();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (<div title="double click to change title" className="tab-title" onDoubleClick={()=>{
|
||||
setEditable(true)
|
||||
}}>
|
||||
{editable ? <input ref={inputRef} className="input-eidtor"
|
||||
{editable ? <input ref={inputRef} className="input-eidtor" onKeyDown={onKeyDown}
|
||||
type="text" value={value}
|
||||
onBlur={()=>{
|
||||
setEditable(false)
|
||||
}}
|
||||
onChange={onValueChange}/>:
|
||||
<div style={{display:'flex', alignItems:'center'}}><Icon component={ElasticIcon} />{value}</div>}
|
||||
<div className="icon-cont"><Icon component={ElasticIcon} />{value}</div>}
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue